@deepkit/http 1.0.1-alpha.16 → 1.0.1-alpha.160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +236 -0
- package/dist/cjs/index.d.ts +14 -10
- package/dist/cjs/index.js +19 -11
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/src/controllers.d.ts +10 -3
- package/dist/cjs/src/controllers.js +7 -3
- package/dist/cjs/src/controllers.js.map +1 -1
- package/dist/cjs/src/decorator.d.ts +144 -70
- package/dist/cjs/src/decorator.js +277 -102
- package/dist/cjs/src/decorator.js.map +1 -1
- package/dist/cjs/src/filter.d.ts +49 -0
- package/dist/cjs/src/filter.js +215 -0
- package/dist/cjs/src/filter.js.map +1 -0
- package/dist/cjs/src/http-app.d.ts +8 -0
- package/dist/cjs/src/http-app.js +16 -0
- package/dist/cjs/src/http-app.js.map +1 -0
- package/dist/cjs/src/http.d.ts +131 -32
- package/dist/cjs/src/http.js +523 -122
- package/dist/cjs/src/http.js.map +1 -1
- package/dist/cjs/src/kernel.d.ts +50 -9
- package/dist/cjs/src/kernel.js +109 -98
- package/dist/cjs/src/kernel.js.map +1 -1
- package/dist/cjs/src/logger.d.ts +3 -3
- package/dist/cjs/src/logger.js +9 -12
- package/dist/cjs/src/logger.js.map +1 -1
- package/dist/cjs/src/middleware.d.ts +61 -0
- package/dist/cjs/src/middleware.js +95 -0
- package/dist/cjs/src/middleware.js.map +1 -0
- package/dist/cjs/src/model.d.ts +270 -4
- package/dist/cjs/src/model.js +401 -3
- package/dist/cjs/src/model.js.map +1 -1
- package/dist/cjs/src/module.config.d.ts +149 -4
- package/dist/cjs/src/module.config.js +11 -6
- package/dist/cjs/src/module.config.js.map +1 -1
- package/dist/cjs/src/module.d.ts +14 -32
- package/dist/cjs/src/module.js +122 -7
- package/dist/cjs/src/module.js.map +1 -1
- package/dist/cjs/src/request-parser.d.ts +37 -0
- package/dist/cjs/src/request-parser.js +382 -0
- package/dist/cjs/src/request-parser.js.map +1 -0
- package/dist/cjs/src/router.d.ts +174 -75
- package/dist/cjs/src/router.js +469 -288
- package/dist/cjs/src/router.js.map +1 -1
- package/dist/cjs/src/static-serving.d.ts +32 -4
- package/dist/cjs/src/static-serving.js +115 -71
- package/dist/cjs/src/static-serving.js.map +1 -1
- package/dist/cjs/src/utils.js +2 -2
- package/dist/cjs/src/utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -2916
- package/dist/esm/index.d.ts +14 -10
- package/dist/esm/index.js +14 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/src/controllers.d.ts +10 -3
- package/dist/esm/src/controllers.js +7 -3
- package/dist/esm/src/controllers.js.map +1 -1
- package/dist/esm/src/decorator.d.ts +144 -70
- package/dist/esm/src/decorator.js +272 -99
- package/dist/esm/src/decorator.js.map +1 -1
- package/dist/esm/src/filter.d.ts +49 -0
- package/dist/esm/src/filter.js +209 -0
- package/dist/esm/src/filter.js.map +1 -0
- package/dist/esm/src/http-app.d.ts +8 -0
- package/dist/esm/src/http-app.js +12 -0
- package/dist/esm/src/http-app.js.map +1 -0
- package/dist/esm/src/http.d.ts +131 -32
- package/dist/esm/src/http.js +504 -121
- package/dist/esm/src/http.js.map +1 -1
- package/dist/esm/src/kernel.d.ts +50 -9
- package/dist/esm/src/kernel.js +110 -100
- package/dist/esm/src/kernel.js.map +1 -1
- package/dist/esm/src/logger.d.ts +3 -3
- package/dist/esm/src/logger.js +7 -11
- package/dist/esm/src/logger.js.map +1 -1
- package/dist/esm/src/middleware.d.ts +61 -0
- package/dist/esm/src/middleware.js +90 -0
- package/dist/esm/src/middleware.js.map +1 -0
- package/dist/esm/src/model.d.ts +270 -4
- package/dist/esm/src/model.js +357 -1
- package/dist/esm/src/model.js.map +1 -1
- package/dist/esm/src/module.config.d.ts +149 -4
- package/dist/esm/src/module.config.js +9 -5
- package/dist/esm/src/module.config.js.map +1 -1
- package/dist/esm/src/module.d.ts +14 -32
- package/dist/esm/src/module.js +121 -7
- package/dist/esm/src/module.js.map +1 -1
- package/dist/esm/src/request-parser.d.ts +37 -0
- package/dist/esm/src/request-parser.js +372 -0
- package/dist/esm/src/request-parser.js.map +1 -0
- package/dist/esm/src/router.d.ts +174 -75
- package/dist/esm/src/router.js +463 -285
- package/dist/esm/src/router.js.map +1 -1
- package/dist/esm/src/static-serving.d.ts +32 -4
- package/dist/esm/src/static-serving.js +107 -65
- package/dist/esm/src/static-serving.js.map +1 -1
- package/dist/esm/src/utils.js +1 -0
- package/dist/esm/src/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -2916
- package/index.ts +14 -10
- package/package.json +30 -19
- package/src/controllers.ts +4 -3
- package/src/decorator.ts +242 -104
- package/src/filter.ts +224 -0
- package/src/http-app.ts +11 -0
- package/src/http.ts +501 -100
- package/src/kernel.ts +128 -80
- package/src/logger.ts +4 -6
- package/src/middleware.ts +116 -0
- package/src/model.ts +516 -5
- package/src/module.config.ts +168 -5
- package/src/module.ts +120 -7
- package/src/request-parser.ts +430 -0
- package/src/router.ts +546 -317
- package/src/static-serving.ts +120 -50
- package/src/utils.ts +0 -2
- package/tsconfig.esm.json +7 -7
- package/tsconfig.json +18 -11
- package/tsconfig.spec.json +8 -0
- package/dist/cjs/tests/router.spec.d.ts +0 -1
- package/dist/cjs/tests/router.spec.js +0 -481
- package/dist/cjs/tests/router.spec.js.map +0 -1
- package/dist/esm/tests/router.spec.d.ts +0 -1
- package/dist/esm/tests/router.spec.js +0 -479
- package/dist/esm/tests/router.spec.js.map +0 -1
- package/tests/router.spec.ts +0 -347
package/src/http.ts
CHANGED
|
@@ -8,16 +8,54 @@
|
|
|
8
8
|
* You should have received a copy of the MIT License along with this program.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import {
|
|
12
|
+
asyncOperation,
|
|
13
|
+
ClassType,
|
|
14
|
+
CustomError,
|
|
15
|
+
getClassName,
|
|
16
|
+
getClassTypeFromInstance,
|
|
17
|
+
isArray,
|
|
18
|
+
isClassInstance,
|
|
19
|
+
isObject,
|
|
20
|
+
} from '@deepkit/core';
|
|
21
|
+
import { OutgoingHttpHeaders, ServerResponse } from 'http';
|
|
14
22
|
import { eventDispatcher } from '@deepkit/event';
|
|
15
|
-
import { HttpRequest, HttpResponse } from './model';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { RouteConfig, RouteParameterResolverForInjector
|
|
23
|
+
import { HttpRequest, HttpRequestPositionedParameters, HttpResponse } from './model.js';
|
|
24
|
+
import { InjectorContext } from '@deepkit/injector';
|
|
25
|
+
import { LoggerInterface } from '@deepkit/logger';
|
|
26
|
+
import { HttpRouter, RouteConfig, RouteParameterResolverForInjector } from './router.js';
|
|
19
27
|
import { createWorkflow, WorkflowEvent } from '@deepkit/workflow';
|
|
20
|
-
import {
|
|
28
|
+
import type { ElementStruct, render } from '@deepkit/template';
|
|
29
|
+
import { FrameCategory, Stopwatch } from '@deepkit/stopwatch';
|
|
30
|
+
import {
|
|
31
|
+
getSerializeFunction,
|
|
32
|
+
hasTypeInformation,
|
|
33
|
+
ReflectionKind,
|
|
34
|
+
resolveReceiveType,
|
|
35
|
+
SerializationError,
|
|
36
|
+
serialize,
|
|
37
|
+
serializer,
|
|
38
|
+
Type,
|
|
39
|
+
typeSettings,
|
|
40
|
+
UnpopulatedCheck,
|
|
41
|
+
ValidationError,
|
|
42
|
+
} from '@deepkit/type';
|
|
43
|
+
import stream from 'stream';
|
|
44
|
+
|
|
45
|
+
export function isElementStruct(v: any): v is ElementStruct {
|
|
46
|
+
return isObject(v) && 'hasOwnProperty' in v && v.hasOwnProperty('render') && v.hasOwnProperty('attributes');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let templateRender: typeof render;
|
|
50
|
+
|
|
51
|
+
function getTemplateRender(): typeof render {
|
|
52
|
+
if (!templateRender) {
|
|
53
|
+
const template = require('@deepkit/template');
|
|
54
|
+
templateRender = template.render;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return templateRender;
|
|
58
|
+
}
|
|
21
59
|
|
|
22
60
|
export class Redirect {
|
|
23
61
|
public routeName?: string;
|
|
@@ -43,42 +81,65 @@ export class Redirect {
|
|
|
43
81
|
}
|
|
44
82
|
}
|
|
45
83
|
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
84
|
+
export class HttpError<T extends number> extends CustomError {
|
|
85
|
+
constructor(public message: string, public httpCode: T) {
|
|
86
|
+
super(message);
|
|
87
|
+
}
|
|
50
88
|
}
|
|
51
89
|
|
|
52
|
-
export function
|
|
53
|
-
return class extends
|
|
54
|
-
constructor(message: string = defaultMessage) {
|
|
55
|
-
super(message);
|
|
90
|
+
export function createHttpError<T extends number>(code: T, defaultMessage: string = ''): ClassType<HttpError<T>> {
|
|
91
|
+
return class extends HttpError<T> {
|
|
92
|
+
constructor(public message: string = defaultMessage) {
|
|
93
|
+
super(message, code);
|
|
56
94
|
}
|
|
95
|
+
} as any;
|
|
96
|
+
}
|
|
57
97
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
export class HttpBadRequestError extends createHttpError(400, 'Bad request') {
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class HttpUnauthorizedError extends createHttpError(401, 'Unauthorized') {
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class HttpAccessDeniedError extends createHttpError(403, 'Access denied') {
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class HttpNotFoundError extends createHttpError(404, 'Not found') {
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class HttpMethodNotAllowedError extends createHttpError(405, 'Method not allowed') {
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class HttpNotAcceptableError extends createHttpError(406, 'Not acceptable') {
|
|
62
114
|
}
|
|
63
115
|
|
|
64
|
-
export class
|
|
116
|
+
export class HttpTimeoutError extends createHttpError(408, 'Request timeout') {
|
|
65
117
|
}
|
|
66
118
|
|
|
67
|
-
export class
|
|
119
|
+
export class HttpConflictError extends createHttpError(409, 'Conflict') {
|
|
68
120
|
}
|
|
69
121
|
|
|
70
|
-
export class
|
|
122
|
+
export class HttpGoneError extends createHttpError(410, 'Gone') {
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class HttpTooManyRequestsError extends createHttpError(429, 'Too many requests') {
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export class HttpInternalServerError extends createHttpError(500, 'Internal server error') {
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class HttpNotImplementedError extends createHttpError(501, 'Not implemented') {
|
|
71
132
|
}
|
|
72
133
|
|
|
73
134
|
export class HttpWorkflowEvent {
|
|
74
|
-
|
|
135
|
+
propagationStopped = false;
|
|
75
136
|
|
|
76
137
|
stopPropagation() {
|
|
77
|
-
this.
|
|
138
|
+
this.propagationStopped = true;
|
|
78
139
|
}
|
|
79
140
|
|
|
80
|
-
|
|
81
|
-
return this.
|
|
141
|
+
isPropagationStopped() {
|
|
142
|
+
return this.propagationStopped;
|
|
82
143
|
}
|
|
83
144
|
|
|
84
145
|
public nextState?: any;
|
|
@@ -97,6 +158,9 @@ export class HttpWorkflowEvent {
|
|
|
97
158
|
this.nextStateEvent = event;
|
|
98
159
|
}
|
|
99
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Whether already a next workflow state has been scheduled.
|
|
163
|
+
*/
|
|
100
164
|
hasNext(): boolean {
|
|
101
165
|
return this.nextState !== undefined;
|
|
102
166
|
}
|
|
@@ -140,6 +204,10 @@ export class HttpWorkflowEventWithRoute extends HttpWorkflowEvent {
|
|
|
140
204
|
send(response: any) {
|
|
141
205
|
this.next('response', new HttpResponseEvent(this.injectorContext, this.request, this.response, response, this.route));
|
|
142
206
|
}
|
|
207
|
+
|
|
208
|
+
accessDenied(error?: Error) {
|
|
209
|
+
this.next('accessDenied', new HttpAccessDeniedEvent(this.injectorContext, this.request, this.response, this.route, error));
|
|
210
|
+
}
|
|
143
211
|
}
|
|
144
212
|
|
|
145
213
|
export class HttpRouteEvent extends HttpWorkflowEvent {
|
|
@@ -177,10 +245,6 @@ export class HttpAuthEvent extends HttpWorkflowEventWithRoute {
|
|
|
177
245
|
success() {
|
|
178
246
|
this.next('resolveParameters', new HttpResolveParametersEvent(this.injectorContext, this.request, this.response, this.parameterResolver, this.route));
|
|
179
247
|
}
|
|
180
|
-
|
|
181
|
-
accessDenied() {
|
|
182
|
-
this.next('accessDenied', new HttpAccessDeniedEvent(this.injectorContext, this.request, this.response, this.route));
|
|
183
|
-
}
|
|
184
248
|
}
|
|
185
249
|
|
|
186
250
|
export class HttpAccessDeniedEvent extends HttpWorkflowEvent {
|
|
@@ -189,13 +253,14 @@ export class HttpAccessDeniedEvent extends HttpWorkflowEvent {
|
|
|
189
253
|
public request: HttpRequest,
|
|
190
254
|
public response: HttpResponse,
|
|
191
255
|
public route: RouteConfig,
|
|
256
|
+
public error?: Error,
|
|
192
257
|
) {
|
|
193
258
|
super(injectorContext, request, response);
|
|
194
259
|
}
|
|
195
260
|
}
|
|
196
261
|
|
|
197
|
-
export class HttpResolveParametersEvent extends
|
|
198
|
-
public parameters:
|
|
262
|
+
export class HttpResolveParametersEvent extends HttpWorkflowEventWithRoute {
|
|
263
|
+
public parameters: HttpRequestPositionedParameters = { arguments: [], parameters: {} };
|
|
199
264
|
|
|
200
265
|
constructor(
|
|
201
266
|
public injectorContext: InjectorContext,
|
|
@@ -204,23 +269,32 @@ export class HttpResolveParametersEvent extends HttpWorkflowEvent {
|
|
|
204
269
|
public parameterResolver: RouteParameterResolverForInjector,
|
|
205
270
|
public route: RouteConfig,
|
|
206
271
|
) {
|
|
207
|
-
super(injectorContext, request, response);
|
|
272
|
+
super(injectorContext, request, response, route);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
accessDenied() {
|
|
276
|
+
this.next('accessDenied', new HttpAccessDeniedEvent(this.injectorContext, this.request, this.response, this.route));
|
|
208
277
|
}
|
|
209
278
|
}
|
|
210
279
|
|
|
211
|
-
export class HttpControllerEvent extends
|
|
280
|
+
export class HttpControllerEvent extends HttpWorkflowEventWithRoute {
|
|
212
281
|
constructor(
|
|
213
282
|
public injectorContext: InjectorContext,
|
|
214
283
|
public request: HttpRequest,
|
|
215
284
|
public response: HttpResponse,
|
|
216
|
-
public parameters:
|
|
285
|
+
public parameters: HttpRequestPositionedParameters = { arguments: [], parameters: {} },
|
|
217
286
|
public route: RouteConfig,
|
|
218
287
|
) {
|
|
219
|
-
super(injectorContext, request, response);
|
|
288
|
+
super(injectorContext, request, response, route);
|
|
220
289
|
}
|
|
221
290
|
}
|
|
222
291
|
|
|
223
292
|
export class HttpResponseEvent extends WorkflowEvent {
|
|
293
|
+
/**
|
|
294
|
+
* The time it took to call the controller action in milliseconds.
|
|
295
|
+
*/
|
|
296
|
+
public controllerActionTime: number = 0;
|
|
297
|
+
|
|
224
298
|
constructor(
|
|
225
299
|
public injectorContext: InjectorContext,
|
|
226
300
|
public request: HttpRequest,
|
|
@@ -232,7 +306,12 @@ export class HttpResponseEvent extends WorkflowEvent {
|
|
|
232
306
|
}
|
|
233
307
|
}
|
|
234
308
|
|
|
235
|
-
export class HttpControllerErrorEvent extends
|
|
309
|
+
export class HttpControllerErrorEvent extends HttpWorkflowEventWithRoute {
|
|
310
|
+
/**
|
|
311
|
+
* The time it took to call the controller action in milliseconds.
|
|
312
|
+
*/
|
|
313
|
+
public controllerActionTime: number = 0;
|
|
314
|
+
|
|
236
315
|
constructor(
|
|
237
316
|
public injectorContext: InjectorContext,
|
|
238
317
|
public request: HttpRequest,
|
|
@@ -240,7 +319,7 @@ export class HttpControllerErrorEvent extends HttpWorkflowEvent {
|
|
|
240
319
|
public route: RouteConfig,
|
|
241
320
|
public error: Error,
|
|
242
321
|
) {
|
|
243
|
-
super(injectorContext, request, response);
|
|
322
|
+
super(injectorContext, request, response, route);
|
|
244
323
|
}
|
|
245
324
|
}
|
|
246
325
|
|
|
@@ -269,22 +348,217 @@ export const httpWorkflow = createWorkflow('http', {
|
|
|
269
348
|
routeNotFound: 'response',
|
|
270
349
|
});
|
|
271
350
|
|
|
272
|
-
export class
|
|
273
|
-
|
|
351
|
+
export class BaseResponse {
|
|
352
|
+
autoSerializing: boolean = true;
|
|
353
|
+
|
|
354
|
+
constructor(public _statusCode?: number, public _headers: OutgoingHttpHeaders = {}) {
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
status(code: number): this {
|
|
358
|
+
this._statusCode = code;
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Per default a JSONResponse is serialized using the return type specified at the route.
|
|
364
|
+
* This disables that behaviour so that JSON.stringify is run on the result directly.
|
|
365
|
+
*/
|
|
366
|
+
disableAutoSerializing() {
|
|
367
|
+
this.autoSerializing = false;
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
header(name: string, value: string | number): this {
|
|
372
|
+
this._headers[name] = value;
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
headers(headers: OutgoingHttpHeaders): this {
|
|
377
|
+
this._headers = headers;
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
contentType(type: string): this {
|
|
382
|
+
this._headers['content-type'] = type;
|
|
383
|
+
return this;
|
|
274
384
|
}
|
|
275
385
|
}
|
|
276
386
|
|
|
277
|
-
export class
|
|
278
|
-
constructor(public
|
|
387
|
+
export class Response extends BaseResponse {
|
|
388
|
+
constructor(public content: string | Uint8Array, contentType: string, statusCode?: number) {
|
|
389
|
+
super(statusCode);
|
|
390
|
+
this.contentType(contentType);
|
|
279
391
|
}
|
|
280
392
|
}
|
|
281
393
|
|
|
394
|
+
export class HtmlResponse extends BaseResponse {
|
|
395
|
+
constructor(public html: string, statusCode?: number) {
|
|
396
|
+
super(statusCode);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export class JSONResponse extends BaseResponse {
|
|
401
|
+
constructor(public json: any, statusCode?: number) {
|
|
402
|
+
super(statusCode);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export type SupportedHttpResult =
|
|
407
|
+
undefined
|
|
408
|
+
| null
|
|
409
|
+
| number
|
|
410
|
+
| string
|
|
411
|
+
| Response
|
|
412
|
+
| JSONResponse
|
|
413
|
+
| HtmlResponse
|
|
414
|
+
| HttpResponse
|
|
415
|
+
| ServerResponse
|
|
416
|
+
| stream.Readable
|
|
417
|
+
| Redirect
|
|
418
|
+
| Uint8Array
|
|
419
|
+
| Error;
|
|
420
|
+
|
|
421
|
+
export interface HttpResultFormatterContext {
|
|
422
|
+
request: HttpRequest;
|
|
423
|
+
response: HttpResponse;
|
|
424
|
+
route?: RouteConfig;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export class HttpResultFormatter {
|
|
428
|
+
protected jsonContentType: string = 'application/json; charset=utf-8';
|
|
429
|
+
protected htmlContentType: string = 'text/html; charset=utf-8';
|
|
430
|
+
|
|
431
|
+
constructor(protected router: HttpRouter) {
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
protected setContentTypeIfNotSetAlready(response: HttpResponse, contentType: string): void {
|
|
435
|
+
if (response.hasHeader('Content-Type')) return;
|
|
436
|
+
|
|
437
|
+
response.setHeader('Content-Type', contentType);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
handleError(error: Error, context: HttpResultFormatterContext): void {
|
|
441
|
+
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
handleUndefined(result: undefined | null, context: HttpResultFormatterContext): void {
|
|
445
|
+
context.response.end(result);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
handleRedirect(result: Redirect, context: HttpResultFormatterContext): void {
|
|
449
|
+
if (result.routeName) {
|
|
450
|
+
context.response.writeHead(result.statusCode, {
|
|
451
|
+
Location: this.router.resolveUrl(result.routeName, result.routeParameters)
|
|
452
|
+
});
|
|
453
|
+
} else {
|
|
454
|
+
context.response.writeHead(result.statusCode, {
|
|
455
|
+
Location: result.url
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
context.response.end();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
handleUnknown(result: any, context: HttpResultFormatterContext): void {
|
|
462
|
+
const oldCheck = typeSettings.unpopulatedCheck;
|
|
463
|
+
try {
|
|
464
|
+
typeSettings.unpopulatedCheck = UnpopulatedCheck.None;
|
|
465
|
+
this.setContentTypeIfNotSetAlready(context.response, this.jsonContentType);
|
|
466
|
+
context.response.end(JSON.stringify(result));
|
|
467
|
+
} finally {
|
|
468
|
+
typeSettings.unpopulatedCheck = oldCheck;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
handleHtmlResponse(result: HtmlResponse, context: HttpResultFormatterContext): void {
|
|
473
|
+
this.setContentTypeIfNotSetAlready(context.response, this.htmlContentType);
|
|
474
|
+
context.response.writeHead(result._statusCode || 200, result._headers);
|
|
475
|
+
context.response.end(result.html);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
handleGenericResponse(result: Response, context: HttpResultFormatterContext): void {
|
|
479
|
+
context.response.writeHead(result._statusCode || 200, result._headers);
|
|
480
|
+
context.response.end(result.content);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
handleJSONResponse(result: JSONResponse, context: HttpResultFormatterContext): void {
|
|
484
|
+
this.setContentTypeIfNotSetAlready(context.response, this.jsonContentType);
|
|
485
|
+
context.response.writeHead(result._statusCode || 200, result._headers);
|
|
486
|
+
context.response.end(JSON.stringify(result.json));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
handleTypeEntity<T>(classType: ClassType<T>, instance: T, context: HttpResultFormatterContext, route?: RouteConfig): void {
|
|
490
|
+
this.handleType(resolveReceiveType(classType), instance, context, route);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
handleType<T>(type: Type, instance: T, context: HttpResultFormatterContext, route?: RouteConfig): void {
|
|
494
|
+
this.setContentTypeIfNotSetAlready(context.response, this.jsonContentType);
|
|
495
|
+
const serializerToUse = route && route?.serializer ? route.serializer : serializer;
|
|
496
|
+
context.response.end(JSON.stringify(serialize(instance, route ? route.serializationOptions : undefined, serializerToUse, undefined, type)));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
handleStream(stream: stream.Readable, context: HttpResultFormatterContext): void {
|
|
500
|
+
stream.pipe(context.response);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
handleBinary(result: Uint8Array, context: HttpResultFormatterContext): void {
|
|
504
|
+
context.response.end(result);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
handleResponse(context: HttpResultFormatterContext) {
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
handle(result: SupportedHttpResult, context: HttpResultFormatterContext): void {
|
|
511
|
+
if (result === null || result === undefined) {
|
|
512
|
+
this.handleUndefined(result, context);
|
|
513
|
+
} else if (result instanceof Error) {
|
|
514
|
+
this.handleError(result, context);
|
|
515
|
+
} else if (result instanceof Redirect) {
|
|
516
|
+
this.handleRedirect(result, context);
|
|
517
|
+
} else if (result instanceof ServerResponse) {
|
|
518
|
+
this.handleResponse(context);
|
|
519
|
+
} else if (result instanceof HtmlResponse) {
|
|
520
|
+
this.handleHtmlResponse(result, context);
|
|
521
|
+
} else if (result instanceof stream.Readable) {
|
|
522
|
+
this.handleStream(result, context);
|
|
523
|
+
} else if (result instanceof Uint8Array) {
|
|
524
|
+
this.handleBinary(result, context);
|
|
525
|
+
} else if (result instanceof JSONResponse) {
|
|
526
|
+
this.handleJSONResponse(result, context);
|
|
527
|
+
} else if (result instanceof Response) {
|
|
528
|
+
this.handleGenericResponse(result, context);
|
|
529
|
+
} else {
|
|
530
|
+
if (isClassInstance(result)) {
|
|
531
|
+
const classType = getClassTypeFromInstance(result);
|
|
532
|
+
if (hasTypeInformation(classType)) {
|
|
533
|
+
this.handleTypeEntity(classType, result, context);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
} else if (isArray(result) && result.length > 0 && isClassInstance(result[0])) {
|
|
537
|
+
const firstClassType = getClassTypeFromInstance(result[0]);
|
|
538
|
+
let allSameType = true;
|
|
539
|
+
for (const item of result) {
|
|
540
|
+
if (!isClassInstance(item) || getClassTypeFromInstance(item) !== firstClassType) {
|
|
541
|
+
allSameType = false;
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (allSameType && hasTypeInformation(firstClassType)) {
|
|
546
|
+
this.handleType({ kind: ReflectionKind.array, type: resolveReceiveType(firstClassType) }, result, context);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this.handleUnknown(result, context);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
282
555
|
|
|
283
|
-
@injectable()
|
|
284
556
|
export class HttpListener {
|
|
285
557
|
constructor(
|
|
286
|
-
protected router:
|
|
287
|
-
protected logger:
|
|
558
|
+
protected router: HttpRouter,
|
|
559
|
+
protected logger: LoggerInterface,
|
|
560
|
+
protected resultFormatter: HttpResultFormatter,
|
|
561
|
+
protected stopwatch?: Stopwatch,
|
|
288
562
|
) {
|
|
289
563
|
}
|
|
290
564
|
|
|
@@ -297,13 +571,76 @@ export class HttpListener {
|
|
|
297
571
|
}
|
|
298
572
|
|
|
299
573
|
@eventDispatcher.listen(httpWorkflow.onRoute, 100)
|
|
300
|
-
onRoute(event: typeof httpWorkflow.onRoute.event) {
|
|
574
|
+
async onRoute(event: typeof httpWorkflow.onRoute.event) {
|
|
301
575
|
if (event.sent) return;
|
|
302
576
|
if (event.hasNext()) return;
|
|
577
|
+
const logger = this.logger;
|
|
303
578
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
579
|
+
try {
|
|
580
|
+
const resolved = this.router.resolveRequest(event.request);
|
|
581
|
+
|
|
582
|
+
if (resolved) {
|
|
583
|
+
event.request.uploadedFiles = resolved.uploadedFiles;
|
|
584
|
+
|
|
585
|
+
//todo: embed that into the generated router code.
|
|
586
|
+
if (resolved.middlewares) {
|
|
587
|
+
const middlewares = resolved.middlewares(event.injectorContext);
|
|
588
|
+
if (middlewares.length) {
|
|
589
|
+
await asyncOperation(async (resolve, reject) => {
|
|
590
|
+
let lastTimer: any = undefined;
|
|
591
|
+
|
|
592
|
+
function finish() {
|
|
593
|
+
clearTimeout(lastTimer);
|
|
594
|
+
resolve(undefined);
|
|
595
|
+
//middleware finished the request. We end the workflow transition
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
event.response.once('finish', finish);
|
|
599
|
+
let i = -1;
|
|
600
|
+
|
|
601
|
+
async function next() {
|
|
602
|
+
i++;
|
|
603
|
+
|
|
604
|
+
if (i >= middlewares.length) {
|
|
605
|
+
event.response.off('finish', finish);
|
|
606
|
+
resolve(undefined);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const timeout = middlewares[i].timeout;
|
|
611
|
+
if (timeout !== undefined && timeout > 0) {
|
|
612
|
+
lastTimer = setTimeout(() => {
|
|
613
|
+
logger.warn(`Middleware timed out. Increase the timeout or fix the middleware. (${middlewares[i].fn})`);
|
|
614
|
+
next();
|
|
615
|
+
}, timeout);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
await middlewares[i].fn(event.request, event.response, (error?: any) => {
|
|
620
|
+
if (lastTimer) clearTimeout(lastTimer);
|
|
621
|
+
if (error) {
|
|
622
|
+
event.response.off('finish', finish);
|
|
623
|
+
reject(error);
|
|
624
|
+
} else {
|
|
625
|
+
next();
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
} catch (error) {
|
|
629
|
+
reject(error);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
await next();
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
event.injectorContext.set(RouteConfig, resolved.routeConfig);
|
|
639
|
+
event.routeFound(resolved.routeConfig, resolved.parameters);
|
|
640
|
+
}
|
|
641
|
+
} catch (error) {
|
|
642
|
+
this.logger.error('Could not resolve request', error);
|
|
643
|
+
event.notFound();
|
|
307
644
|
}
|
|
308
645
|
}
|
|
309
646
|
|
|
@@ -322,7 +659,11 @@ export class HttpListener {
|
|
|
322
659
|
if (event.sent) return;
|
|
323
660
|
if (event.hasNext()) return;
|
|
324
661
|
|
|
325
|
-
event.
|
|
662
|
+
if (event.request.throwErrorOnNotFound) {
|
|
663
|
+
throw new HttpNotFoundError;
|
|
664
|
+
} else {
|
|
665
|
+
event.send(new HtmlResponse('Not found', 404));
|
|
666
|
+
}
|
|
326
667
|
}
|
|
327
668
|
|
|
328
669
|
@eventDispatcher.listen(httpWorkflow.onAuth, 100)
|
|
@@ -341,7 +682,7 @@ export class HttpListener {
|
|
|
341
682
|
try {
|
|
342
683
|
event.parameters = await event.parameterResolver(event.injectorContext);
|
|
343
684
|
event.next('controller', new HttpControllerEvent(event.injectorContext, event.request, event.response, event.parameters, event.route));
|
|
344
|
-
} catch (error) {
|
|
685
|
+
} catch (error: any) {
|
|
345
686
|
event.next('parametersFailed', new HttpControllerErrorEvent(event.injectorContext, event.request, event.response, event.route, error));
|
|
346
687
|
}
|
|
347
688
|
}
|
|
@@ -359,16 +700,48 @@ export class HttpListener {
|
|
|
359
700
|
if (event.sent) return;
|
|
360
701
|
if (event.hasNext()) return;
|
|
361
702
|
|
|
362
|
-
|
|
703
|
+
|
|
704
|
+
const start = Date.now();
|
|
705
|
+
const stopWatchLabel = event.route.action.type === 'controller'
|
|
706
|
+
? getClassName(event.route.action.controller) + '.' + event.route.action.methodName
|
|
707
|
+
: event.route.action.fn.name;
|
|
708
|
+
|
|
709
|
+
const frame = this.stopwatch ? this.stopwatch.start(stopWatchLabel, FrameCategory.httpController) : undefined;
|
|
363
710
|
try {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
711
|
+
let result: any;
|
|
712
|
+
if (event.route.action.type === 'controller') {
|
|
713
|
+
const controllerInstance = event.injectorContext.get(event.route.action.controller, event.route.action.module);
|
|
714
|
+
const method = controllerInstance[event.route.action.methodName];
|
|
715
|
+
result = await method.apply(controllerInstance, event.parameters.arguments);
|
|
716
|
+
} else {
|
|
717
|
+
result = await event.route.action.fn(...event.parameters.arguments);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (isElementStruct(result)) {
|
|
721
|
+
const html = await getTemplateRender()(event.injectorContext.getRootInjector(), result, this.stopwatch ? this.stopwatch : undefined);
|
|
722
|
+
result = new HtmlResponse(html, 200).header('Content-Type', 'text/html; charset=utf-8');
|
|
723
|
+
}
|
|
724
|
+
if (result instanceof stream.Readable) {
|
|
725
|
+
const stream = result as stream.Readable;
|
|
726
|
+
await new Promise((resolve, reject) => {
|
|
727
|
+
stream.once('readable', resolve);
|
|
728
|
+
stream.once('error', reject);
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
const responseEvent = new HttpResponseEvent(event.injectorContext, event.request, event.response, result, event.route);
|
|
732
|
+
responseEvent.controllerActionTime = Date.now() - start;
|
|
733
|
+
event.next('response', responseEvent);
|
|
734
|
+
} catch (error: any) {
|
|
735
|
+
if (frame) frame.end();
|
|
367
736
|
if (error instanceof HttpAccessDeniedError) {
|
|
368
|
-
event.next('accessDenied', new HttpAccessDeniedEvent(event.injectorContext, event.request, event.response, event.route));
|
|
737
|
+
event.next('accessDenied', new HttpAccessDeniedEvent(event.injectorContext, event.request, event.response, event.route, error));
|
|
369
738
|
} else {
|
|
370
|
-
|
|
739
|
+
const errorEvent = new HttpControllerErrorEvent(event.injectorContext, event.request, event.response, event.route, error);
|
|
740
|
+
errorEvent.controllerActionTime = Date.now() - start;
|
|
741
|
+
event.next('controllerError', errorEvent);
|
|
371
742
|
}
|
|
743
|
+
} finally {
|
|
744
|
+
if (frame) frame.end();
|
|
372
745
|
}
|
|
373
746
|
}
|
|
374
747
|
|
|
@@ -377,14 +750,28 @@ export class HttpListener {
|
|
|
377
750
|
if (event.response.finished) return;
|
|
378
751
|
if (event.sent) return;
|
|
379
752
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
753
|
+
if (event.error instanceof SerializationError) {
|
|
754
|
+
event.send(new JSONResponse({
|
|
755
|
+
message: event.error.message,
|
|
756
|
+
errors: [{
|
|
757
|
+
path: event.error.path,
|
|
758
|
+
code: 'serialization',
|
|
759
|
+
message: event.error.originalMessage,
|
|
760
|
+
}]
|
|
761
|
+
}, 400).disableAutoSerializing());
|
|
762
|
+
} else if (event.error instanceof ValidationError) {
|
|
383
763
|
event.send(new JSONResponse({
|
|
384
764
|
message: event.error.message,
|
|
385
765
|
errors: event.error.errors
|
|
386
|
-
},
|
|
766
|
+
}, 400).disableAutoSerializing());
|
|
767
|
+
} else if (event.error instanceof HttpError) {
|
|
768
|
+
event.send(new JSONResponse({
|
|
769
|
+
message: event.error.message
|
|
770
|
+
}, event.error.httpCode).disableAutoSerializing());
|
|
771
|
+
return;
|
|
387
772
|
} else {
|
|
773
|
+
this.logger.error(`Controller for route ${event.route.getFullPath()} parameter resolving error:`, event.error);
|
|
774
|
+
|
|
388
775
|
event.send(new HtmlResponse('Internal error', 500));
|
|
389
776
|
}
|
|
390
777
|
}
|
|
@@ -394,50 +781,64 @@ export class HttpListener {
|
|
|
394
781
|
if (event.response.finished) return;
|
|
395
782
|
if (event.sent) return;
|
|
396
783
|
|
|
784
|
+
if (event.error instanceof ValidationError) {
|
|
785
|
+
event.send(new JSONResponse({
|
|
786
|
+
message: event.error.message,
|
|
787
|
+
errors: event.error.errors
|
|
788
|
+
}, 400).disableAutoSerializing());
|
|
789
|
+
return;
|
|
790
|
+
} else if (event.error instanceof HttpError) {
|
|
791
|
+
if (event.request.throwErrorOnNotFound && event.error.httpCode === 404) {
|
|
792
|
+
throw event.error;
|
|
793
|
+
}
|
|
794
|
+
event.send(new JSONResponse({
|
|
795
|
+
message: event.error.message
|
|
796
|
+
}, event.error.httpCode).disableAutoSerializing());
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
|
|
397
800
|
this.logger.error('Controller error', event.error);
|
|
398
801
|
|
|
399
802
|
event.send(new HtmlResponse('Internal error', 500));
|
|
400
803
|
}
|
|
401
804
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
event.
|
|
421
|
-
} else if (
|
|
422
|
-
|
|
423
|
-
event.
|
|
424
|
-
|
|
425
|
-
event.response.end(response.html);
|
|
426
|
-
} else if (isElementStruct(response)) {
|
|
427
|
-
event.response.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
428
|
-
event.response.end(await render(event.injectorContext, response));
|
|
429
|
-
} else if (isClassInstance(response) && isRegisteredEntity(getClassTypeFromInstance(response))) {
|
|
430
|
-
event.response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
431
|
-
event.response.end(JSON.stringify(jsonSerializer.for(getClassTypeFromInstance(response)).serialize(response)));
|
|
432
|
-
} else if (response instanceof Uint8Array) {
|
|
433
|
-
event.response.end(response);
|
|
434
|
-
} else if (response instanceof JSONResponse) {
|
|
435
|
-
event.response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
436
|
-
if (response.statusCode) event.response.writeHead(response.statusCode);
|
|
437
|
-
event.response.end(JSON.stringify(response.json));
|
|
805
|
+
/**
|
|
806
|
+
* This happens before the result is sent.
|
|
807
|
+
*/
|
|
808
|
+
@eventDispatcher.listen(httpWorkflow.onResponse, -100)
|
|
809
|
+
async onResultSerialization(event: typeof httpWorkflow.onResponse.event) {
|
|
810
|
+
if (!event.route) return;
|
|
811
|
+
if (event.response.headersSent) return;
|
|
812
|
+
if (event.result === undefined || event.result === null) return;
|
|
813
|
+
|
|
814
|
+
if (event.result instanceof HtmlResponse || event.result instanceof Response || event.result instanceof ServerResponse || event.result instanceof Redirect || event.result instanceof stream.Readable) {
|
|
815
|
+
// don't do anything
|
|
816
|
+
} else if (event.result instanceof JSONResponse) {
|
|
817
|
+
const schema = (event.result._statusCode && event.route.getSchemaForResponse(event.result._statusCode)) || event.route.returnType;
|
|
818
|
+
|
|
819
|
+
if (!schema || !event.result.autoSerializing) return;
|
|
820
|
+
|
|
821
|
+
const serializerToUse = event.route && event.route.serializer ? event.route.serializer : serializer;
|
|
822
|
+
const serialize = getSerializeFunction(schema, serializerToUse.serializeRegistry);
|
|
823
|
+
event.result.json = serialize(event.result.json, event.route.serializationOptions);
|
|
824
|
+
} else if (event.route.returnType && event.route.returnType.kind !== ReflectionKind.any) {
|
|
825
|
+
const serializerToUse = event.route && event.route.serializer ? event.route.serializer : serializer;
|
|
826
|
+
const serialize = getSerializeFunction(event.route.returnType, serializerToUse.serializeRegistry);
|
|
827
|
+
event.result = serialize(event.result, event.route.serializationOptions);
|
|
438
828
|
} else {
|
|
439
|
-
event.
|
|
440
|
-
|
|
829
|
+
const schema = event.route.getSchemaForResponse(200);
|
|
830
|
+
if (!schema) return;
|
|
831
|
+
|
|
832
|
+
const serializerToUse = event.route && event.route.serializer ? event.route.serializer : serializer;
|
|
833
|
+
const serialize = getSerializeFunction(schema, serializerToUse.serializeRegistry);
|
|
834
|
+
event.result = serialize(event.result, event.route.serializationOptions);
|
|
441
835
|
}
|
|
442
836
|
}
|
|
837
|
+
|
|
838
|
+
@eventDispatcher.listen(httpWorkflow.onResponse, 100)
|
|
839
|
+
async onResponse(event: typeof httpWorkflow.onResponse.event) {
|
|
840
|
+
if (event.response.headersSent) return;
|
|
841
|
+
|
|
842
|
+
this.resultFormatter.handle(event.result, event);
|
|
843
|
+
}
|
|
443
844
|
}
|