@deepkit/http 1.0.1-alpha.100

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