@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/kernel.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { InjectorContext } from '@deepkit/injector';
2
+ import { HttpRouter } from './router.js';
3
+ import { EventDispatcher } from '@deepkit/event';
4
+ import { LoggerInterface } from '@deepkit/logger';
5
+ import { HttpRequest, HttpResponse, MemoryHttpResponse, RequestBuilder } from './model.js';
6
+ import { HttpError, HttpRequestEvent, HttpResultFormatter, httpWorkflow, JSONResponse } from './http.js';
7
+ import { FrameCategory, Stopwatch } from '@deepkit/stopwatch';
8
+ import { unlink } from 'fs';
9
+ import { ValidationError } from '@deepkit/type';
10
+
11
+ export class HttpKernel {
12
+ constructor(
13
+ protected router: HttpRouter,
14
+ protected eventDispatcher: EventDispatcher,
15
+ protected injectorContext: InjectorContext,
16
+ protected logger: LoggerInterface,
17
+ protected stopwatch?: Stopwatch,
18
+ ) {
19
+
20
+ }
21
+
22
+ public async request(requestBuilder: RequestBuilder): Promise<MemoryHttpResponse> {
23
+ const request = requestBuilder.build();
24
+ const response = new MemoryHttpResponse(request);
25
+ response.assignSocket(request.socket);
26
+ await this.handleRequest(request, response);
27
+ return response;
28
+ }
29
+
30
+ async handleRequest(req: HttpRequest, res: HttpResponse) {
31
+ const httpInjectorContext = this.injectorContext.createChildScope('http');
32
+ httpInjectorContext.set(HttpRequest, req);
33
+ httpInjectorContext.set(HttpResponse, res);
34
+ httpInjectorContext.set(InjectorContext, httpInjectorContext);
35
+
36
+ const frame = this.stopwatch ? this.stopwatch.start(req.method + ' ' + req.getUrl(), FrameCategory.http, true) : undefined;
37
+ const workflow = httpWorkflow.create('start', this.eventDispatcher, httpInjectorContext, this.stopwatch);
38
+
39
+ try {
40
+ if (frame) {
41
+ frame.data({ url: req.getUrl(), method: req.getMethod(), clientIp: req.getRemoteAddress() });
42
+ await frame.run({}, () => workflow.apply('request', new HttpRequestEvent(httpInjectorContext, req, res)));
43
+ } else {
44
+ await workflow.apply('request', new HttpRequestEvent(httpInjectorContext, req, res));
45
+ }
46
+ } catch (error: any) {
47
+ if (!res.headersSent) {
48
+
49
+ const resultFormatter = httpInjectorContext.get(HttpResultFormatter);
50
+ if (error instanceof ValidationError) {
51
+ resultFormatter.handle(new JSONResponse({
52
+ message: error.message,
53
+ errors: error.errors
54
+ }, 400).disableAutoSerializing(), { request: req, response: res });
55
+ return;
56
+ } else if (error instanceof HttpError) {
57
+ resultFormatter.handle(new JSONResponse({
58
+ message: error.message
59
+ }, error.httpCode).disableAutoSerializing(), { request: req, response: res });
60
+ return;
61
+ }
62
+
63
+ res.status(500);
64
+ }
65
+
66
+ this.logger.error('HTTP kernel request failed', error);
67
+ } finally {
68
+ for (const file of Object.values(req.uploadedFiles)) {
69
+ unlink(file.path, () => {
70
+ });
71
+ }
72
+
73
+ if (frame) {
74
+ frame.data({ responseStatus: res.statusCode });
75
+ frame.end();
76
+ }
77
+ }
78
+ }
79
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { LoggerInterface } from '@deepkit/logger';
2
+ import { eventDispatcher } from '@deepkit/event';
3
+ import { httpWorkflow } from './http.js';
4
+
5
+ export class HttpLogger {
6
+ constructor(private logger: LoggerInterface) {
7
+ }
8
+
9
+ @eventDispatcher.listen(httpWorkflow.onResponse, 101) //101 is right after 100 default listener
10
+ onHttpRequest(event: typeof httpWorkflow.onResponse.event) {
11
+ this.logger.log(
12
+ event.request.connection.remoteAddress || '0.0.0.0', '-',
13
+ event.request.method,
14
+ `"${event.request.url}"`,
15
+ event.response.statusCode,
16
+ `"${event.request.headers.referer || ''}"`,
17
+ // `"${event.request.headers['user-agent']}"`,
18
+ );
19
+ }
20
+ }
@@ -0,0 +1,116 @@
1
+ import { ClassType, isClass } from '@deepkit/core';
2
+ import { AppModule } from '@deepkit/app';
3
+ import { createFreeDecoratorContext } from '@deepkit/type';
4
+ import { HttpRequest, HttpResponse } from './model.js';
5
+ import { InjectorModule } from '@deepkit/injector';
6
+
7
+ export type HttpMiddlewareFn = (req: HttpRequest, res: HttpResponse, next: (err?: any) => void) => void | Promise<void>;
8
+
9
+ export interface HttpMiddleware {
10
+ execute: HttpMiddlewareFn;
11
+ }
12
+
13
+ export interface HttpMiddlewareRoute {
14
+ path?: string;
15
+ pathRegExp?: RegExp;
16
+ httpMethod?: 'GET' | 'HEAD' | 'POST' | 'PATCH' | 'PUT' | 'DELETE' | 'OPTIONS' | 'TRACE';
17
+ category?: string;
18
+ excludeCategory?: string;
19
+ group?: string;
20
+ excludeGroup?: string;
21
+ }
22
+
23
+ export class HttpMiddlewareConfig {
24
+ name?: string;
25
+ middlewares: (HttpMiddlewareFn | ClassType<HttpMiddleware>)[] = [];
26
+
27
+ routes: HttpMiddlewareRoute[] = [];
28
+ excludeRoutes: HttpMiddlewareRoute[] = [];
29
+
30
+ order: number = 0;
31
+
32
+ controllers: ClassType[] = [];
33
+ excludeControllers: ClassType[] = [];
34
+
35
+ routeNames: string[] = [];
36
+ excludeRouteNames: string[] = [];
37
+
38
+ timeout?: number;
39
+
40
+ modules: InjectorModule<any>[] = [];
41
+
42
+ selfModule: boolean = false;
43
+
44
+ getClassTypes(): ClassType[] {
45
+ const classTypes: ClassType[] = [];
46
+ for (const middleware of this.middlewares) {
47
+ if (isClass(middleware)) classTypes.push(middleware);
48
+ }
49
+
50
+ return classTypes;
51
+ }
52
+ }
53
+
54
+ export class HttpMiddlewareApi {
55
+ t = new HttpMiddlewareConfig;
56
+
57
+ name(name: string) {
58
+ this.t.name = name;
59
+ }
60
+
61
+ for(...middlewares: (HttpMiddlewareFn | ClassType<HttpMiddleware>)[]) {
62
+ this.t.middlewares = middlewares;
63
+ }
64
+
65
+ forRoutes(...routes: HttpMiddlewareRoute[]) {
66
+ this.t.routes = routes;
67
+ }
68
+
69
+ excludeRoutes(...routes: HttpMiddlewareRoute[]) {
70
+ this.t.excludeRoutes = routes;
71
+ }
72
+
73
+ forRouteNames(...names: string[]) {
74
+ this.t.routeNames = names;
75
+ }
76
+
77
+ excludeRouteNames(...names: string[]) {
78
+ this.t.excludeRouteNames = names;
79
+ }
80
+
81
+ /**
82
+ * When the middleware does not respond (either calling next() or sending headers) withing <timeout> milliseconds,
83
+ * automatically the next is executed and warning printed.
84
+ */
85
+ timeout(timeout: number) {
86
+ this.t.timeout = timeout;
87
+ }
88
+
89
+ forControllers(...controllers: ClassType[]) {
90
+ this.t.controllers = controllers;
91
+ }
92
+
93
+ excludeControllers(...controllers: ClassType[]) {
94
+ this.t.excludeControllers = controllers;
95
+ }
96
+
97
+ /**
98
+ * Per default middlewares are executed in the order they were registered. The default order is 0. A lower order means the middleware is executed earlier.
99
+ */
100
+ order(order: number) {
101
+ this.t.order = order;
102
+ }
103
+
104
+ forModules(...modules: AppModule<any>[]) {
105
+ this.t.modules = modules;
106
+ }
107
+
108
+ /**
109
+ * Limit the middleware to the module where this middleware is defined.
110
+ */
111
+ forSelfModules() {
112
+ this.t.selfModule = true;
113
+ }
114
+ }
115
+
116
+ export const httpMiddleware = createFreeDecoratorContext(HttpMiddlewareApi);
package/src/model.ts ADDED
@@ -0,0 +1,392 @@
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 { IncomingMessage, OutgoingHttpHeader, OutgoingHttpHeaders, ServerResponse } from 'http';
12
+ import { UploadedFile } from './router.js';
13
+ import * as querystring from 'querystring';
14
+ import { Writable } from 'stream';
15
+ import { metaAnnotation, ReflectionKind, Type, ValidationErrorItem } from '@deepkit/type';
16
+ import { asyncOperation, isArray } from '@deepkit/core';
17
+
18
+ export class HttpResponse extends ServerResponse {
19
+ status(code: number) {
20
+ this.writeHead(code);
21
+ this.end();
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Reads the body of the request and returns it as a Buffer.
27
+ * The result will be cached in the request object as `request.body`.
28
+ *
29
+ * Deepkit's router will automatically use `request.body` if available.
30
+ */
31
+ export function readBody(request: IncomingMessage): Promise<Buffer> {
32
+ return asyncOperation((resolve, reject) => {
33
+ const chunks: Buffer[] = [];
34
+ request.on('data', (chunk: Buffer) => {
35
+ chunks.push(chunk);
36
+ });
37
+ request.on('end', () => {
38
+ const body = Buffer.concat(chunks);
39
+ //the router uses this now instead of trying to read the body again
40
+ (request as HttpRequest).body = body;
41
+ resolve(body);
42
+ });
43
+ request.on('error', (error: Error) => {
44
+ reject(error);
45
+ });
46
+ });
47
+ }
48
+
49
+ export function createRequestWithCachedBody(request: Partial<IncomingMessage>, body: Buffer): HttpRequest {
50
+ const headers = request.headers;
51
+ const method = request.method;
52
+ const url = request.url;
53
+
54
+ const writable = new Writable({
55
+ write(chunk, encoding, callback) {
56
+ callback();
57
+ },
58
+ writev(chunks, callback) {
59
+ callback();
60
+ }
61
+ });
62
+ return new (class extends HttpRequest {
63
+ url = url;
64
+ method = method;
65
+ position = 0;
66
+
67
+ headers = headers as any;
68
+
69
+ _read(size: number) {
70
+ if (this.complete) {
71
+ this.push(null);
72
+ } else {
73
+ //in Node 18 this seems to be necessary to not trigger the abort event
74
+ this.complete = true;
75
+ this.push(body);
76
+ }
77
+ }
78
+ })(writable as any);
79
+ }
80
+
81
+ export type HttpRequestQuery = { [name: string]: string };
82
+ export type HttpRequestResolvedParameters = { [name: string]: any };
83
+ export type HttpRequestPositionedParameters = { arguments: any[], parameters: HttpRequestResolvedParameters };
84
+
85
+ export class BodyValidationError {
86
+ constructor(
87
+ public readonly errors: ValidationErrorItem[] = []
88
+ ) {
89
+ }
90
+
91
+ hasErrors(prefix?: string): boolean {
92
+ return this.getErrors(prefix).length > 0;
93
+ }
94
+
95
+ getErrors(prefix?: string): ValidationErrorItem[] {
96
+ if (prefix) return this.errors.filter(v => v.path.startsWith(prefix));
97
+
98
+ return this.errors;
99
+ }
100
+
101
+ getErrorsForPath(path: string): ValidationErrorItem[] {
102
+ return this.errors.filter(v => v.path === path);
103
+ }
104
+
105
+ getErrorMessageForPath(path: string): string {
106
+ return this.getErrorsForPath(path).map(v => v.message).join(', ');
107
+ }
108
+ }
109
+
110
+ export class ValidatedBody<T> {
111
+ constructor(public error: BodyValidationError, public value?: T) {
112
+ }
113
+
114
+ valid(): this is { value: T } {
115
+ return this.value !== undefined;
116
+ }
117
+ }
118
+
119
+ export type HttpBody<T> = T & { __meta?: ['httpBody'] };
120
+ export type HttpBodyValidation<T> = ValidatedBody<T> & { __meta?: ['httpBodyValidation'] };
121
+ export type HttpPath<T, Options extends { name?: string } = {}> = T & { __meta?: ['httpPath', Options] };
122
+ export type HttpHeader<T, Options extends { name?: string } = {}> = T & { __meta?: ['httpHeader', Options] };
123
+ export type HttpQuery<T, Options extends { name?: string } = {}> = T & { __meta?: ['httpQuery', Options] };
124
+ export type HttpQueries<T, Options extends { name?: string } = {}> = T & { __meta?: ['httpQueries', Options] };
125
+
126
+ /**
127
+ * For all parameters used in the URL path, a regular expression of /[^/]+/ is used. To change that, use getRegExp.
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ *
132
+ * class Controller {
133
+ * @http.GET('/user/:username')
134
+ * route(username: HttpRegExp<string, '.*'>) {
135
+ * //username is string
136
+ * }
137
+ * }
138
+ * ```
139
+ */
140
+ export type HttpRegExp<T, Pattern extends string | RegExp> = T & { __meta?: ['httpRegExp', Pattern] };
141
+
142
+ export function getRegExp(type: Type): string | RegExp | undefined {
143
+ const options = metaAnnotation.getForName(type, 'httpRegExp');
144
+ if (!options || !options[0]) return;
145
+ if (options[0].kind === ReflectionKind.literal && 'string' === typeof options[0].literal) return options[0].literal;
146
+ if (options[0].kind === ReflectionKind.literal && options[0].literal instanceof RegExp) return options[0].literal;
147
+ return;
148
+ }
149
+
150
+ export class RequestBuilder {
151
+ protected contentBuffer: Buffer = Buffer.alloc(0);
152
+ protected _headers: { [name: string]: string } = {};
153
+ protected queryPath?: string;
154
+
155
+ constructor(
156
+ protected path: string,
157
+ protected method: string = 'GET',
158
+ ) {
159
+ }
160
+
161
+ getUrl() {
162
+ if (this.queryPath) {
163
+ return this.path + '?' + this.queryPath;
164
+ }
165
+ return this.path;
166
+ }
167
+
168
+ build(): HttpRequest {
169
+ return createRequestWithCachedBody({
170
+ method: this.method,
171
+ url: this.getUrl(),
172
+ headers: this._headers,
173
+ }, this.contentBuffer);
174
+ }
175
+
176
+ headers(headers: { [name: string]: string }): this {
177
+ this._headers = headers;
178
+ return this;
179
+ }
180
+
181
+ header(name: string, value: string | number): this {
182
+ this._headers[name] = String(value);
183
+ return this;
184
+ }
185
+
186
+ json(body: object): this {
187
+ this.contentBuffer = Buffer.from(JSON.stringify(body), 'utf8');
188
+ this._headers['content-type'] = 'application/json; charset=utf-8';
189
+ this._headers['content-length'] = String(this.contentBuffer.byteLength);
190
+ return this;
191
+ }
192
+
193
+ multiPart(items: { name: string, file?: Uint8Array, fileName?: string, json?: any }[]): this {
194
+ const boundary = '--------------------------' + Math.random().toString(36).substr(2, 10);
195
+ this._headers['content-type'] = 'multipart/form-data; boundary=' + boundary;
196
+ const parts = items.map(item => {
197
+ if (item.file) {
198
+ const header = Buffer.from(`--${boundary}\r
199
+ Content-Disposition: form-data; name="${item.name}"; filename="${item.fileName || 'file'}"\r
200
+ Content-Type: application/octet-stream\r
201
+ \r\n`, 'utf8');
202
+ return Buffer.concat([header, item.file, Buffer.from('\r\n', 'utf8')]);
203
+ } else if (item.json) {
204
+ const header = Buffer.from(`--${boundary}\r
205
+ Content-Disposition: form-data; name="${item.name}"\r
206
+ Content-Type: application/json\r
207
+ \r\n`, 'utf8');
208
+ return Buffer.concat([header, Buffer.from(JSON.stringify(item.json) + '\r\n', 'utf8')]);
209
+ } else {
210
+ throw new Error('Invalid multiPart item');
211
+ }
212
+ });
213
+ parts.push(Buffer.from(`--${boundary}--`, 'utf8'));
214
+ this.contentBuffer = Buffer.concat(parts);
215
+ this._headers['content-length'] = String(this.contentBuffer.byteLength);
216
+ return this;
217
+ }
218
+
219
+ body(body: string | Buffer): this {
220
+ if ('string' === typeof body) {
221
+ this.contentBuffer = Buffer.from(body, 'utf8');
222
+ } else {
223
+ this.contentBuffer = body;
224
+ }
225
+ this._headers['content-length'] = String(this.contentBuffer.byteLength);
226
+ return this;
227
+ }
228
+
229
+ query(query: any): this {
230
+ this.queryPath = querystring.stringify(query);
231
+ return this;
232
+ }
233
+ }
234
+
235
+ export class HttpRequest extends IncomingMessage {
236
+ /**
237
+ * A store that can be used to transport data from guards/listeners to ParameterResolvers/controllers.
238
+ */
239
+ public store: { [name: string]: any } = {};
240
+
241
+ public uploadedFiles: { [name: string]: UploadedFile } = {};
242
+
243
+ /**
244
+ * The router sets the body when it was read.
245
+ */
246
+ public body?: Buffer;
247
+
248
+ async readBody(): Promise<Buffer> {
249
+ if (this.body) return this.body;
250
+ return await readBody(this);
251
+ }
252
+
253
+ async readBodyText(): Promise<string> {
254
+ return (await this.readBody()).toString('utf8');
255
+ }
256
+
257
+ static GET(path: string): RequestBuilder {
258
+ return new RequestBuilder(path);
259
+ }
260
+
261
+ static POST(path: string): RequestBuilder {
262
+ return new RequestBuilder(path, 'POST');
263
+ }
264
+
265
+ static OPTIONS(path: string): RequestBuilder {
266
+ return new RequestBuilder(path, 'OPTIONS');
267
+ }
268
+
269
+ static TRACE(path: string): RequestBuilder {
270
+ return new RequestBuilder(path, 'TRACE');
271
+ }
272
+
273
+ static HEAD(path: string): RequestBuilder {
274
+ return new RequestBuilder(path, 'HEAD');
275
+ }
276
+
277
+ static PATCH(path: string): RequestBuilder {
278
+ return new RequestBuilder(path, 'PATCH');
279
+ }
280
+
281
+ static PUT(path: string): RequestBuilder {
282
+ return new RequestBuilder(path, 'PUT');
283
+ }
284
+
285
+ static DELETE(path: string): RequestBuilder {
286
+ return new RequestBuilder(path, 'DELETE');
287
+ }
288
+
289
+ getUrl(): string {
290
+ return this.url || '/';
291
+ }
292
+
293
+ getMethod(): string {
294
+ return this.method || 'GET';
295
+ }
296
+
297
+ getRemoteAddress(): string {
298
+ return this.socket.remoteAddress || '';
299
+ }
300
+ }
301
+
302
+ export class MemoryHttpResponse extends HttpResponse {
303
+ public body: Buffer = Buffer.alloc(0);
304
+ public headers: { [name: string]: number | string | string[] | undefined } = Object.create(null);
305
+
306
+ setHeader(name: string, value: number | string | ReadonlyArray<string>): this {
307
+ this.headers[name] = value as any;
308
+ super.setHeader(name, value);
309
+ return this;
310
+ }
311
+
312
+ removeHeader(name: string) {
313
+ delete this.headers[name];
314
+ super.removeHeader(name);
315
+ }
316
+
317
+ getHeader(name: string) {
318
+ return this.headers[name];
319
+ }
320
+
321
+ getHeaders(): OutgoingHttpHeaders {
322
+ return this.headers;
323
+ }
324
+
325
+ writeHead(statusCode: number, headersOrReasonPhrase?: string | OutgoingHttpHeaders | OutgoingHttpHeader[], headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this {
326
+ headers = typeof headersOrReasonPhrase === 'string' ? headers : headersOrReasonPhrase;
327
+ if (headers && !isArray(headers)) this.headers = headers;
328
+
329
+ if (typeof headersOrReasonPhrase === 'string') return super.writeHead(statusCode, headersOrReasonPhrase, headers);
330
+ return super.writeHead(statusCode, headers);
331
+ }
332
+
333
+ get json(): any {
334
+ const json = this.bodyString;
335
+ try {
336
+ return JSON.parse(json);
337
+ } catch (error: any) {
338
+ throw new Error(`Could not parse JSON: ${error.message}, body: ${json}`);
339
+ }
340
+ }
341
+
342
+ get text(): string {
343
+ return this.bodyString;
344
+ }
345
+
346
+ get bodyString(): string {
347
+ return this.body.toString('utf8');
348
+ }
349
+
350
+ write(
351
+ chunk: any,
352
+ encoding: any,
353
+ callback?: any
354
+ ): any {
355
+ if (typeof encoding === 'function') {
356
+ callback = encoding;
357
+ encoding = null;
358
+ }
359
+
360
+ if (chunk) {
361
+ if ('string' === typeof chunk) {
362
+ chunk = Buffer.from(chunk, encoding || 'utf8');
363
+ }
364
+ this.body = Buffer.concat([this.body, chunk]);
365
+ }
366
+
367
+ if (callback) callback();
368
+ return true;
369
+ }
370
+
371
+ end(chunk: any, encoding?: any, callback?: any): any {
372
+ if (typeof chunk === 'function') {
373
+ callback = chunk;
374
+ chunk = null;
375
+ encoding = null;
376
+ } else if (typeof encoding === 'function') {
377
+ callback = encoding;
378
+ encoding = null;
379
+ }
380
+
381
+ if (chunk) {
382
+ if ('string' === typeof chunk) {
383
+ chunk = Buffer.from(chunk, encoding || 'utf8');
384
+ }
385
+ this.body = Buffer.concat([this.body, chunk]);
386
+ }
387
+ this.emit('finish');
388
+ super.end(chunk, encoding, callback);
389
+ this.emit('close');
390
+ return this;
391
+ }
392
+ }
@@ -0,0 +1,95 @@
1
+ export interface HttpParserOptions {
2
+ /**
3
+ * sets encoding for incoming form fields
4
+ *
5
+ * @default 'utf-8'
6
+ */
7
+ encoding?: BufferEncoding | undefined;
8
+
9
+ /**
10
+ * the directory for placing file uploads in. You can move them later by using fs.rename()
11
+ *
12
+ * @default os.tmpdir()
13
+ */
14
+ uploadDir?: string | undefined;
15
+
16
+ /**
17
+ * to include the extensions of the original files or not
18
+ *
19
+ * @default false
20
+ */
21
+ keepExtensions?: boolean | undefined;
22
+
23
+ /**
24
+ * allow upload empty files
25
+ *
26
+ * @default true
27
+ */
28
+ allowEmptyFiles?: boolean | undefined;
29
+
30
+ /**
31
+ * the minium size of uploaded file
32
+ *
33
+ * @default 1
34
+ */
35
+ minFileSize?: number | undefined;
36
+
37
+ /**
38
+ * limit the amount of uploaded files, set Infinity for unlimited
39
+ *
40
+ * @default Infinity
41
+ */
42
+ maxFiles?: number | undefined;
43
+
44
+ /**
45
+ * limit the size of uploaded file
46
+ *
47
+ * @default 200 * 1024 * 1024
48
+ */
49
+ maxFileSize?: number | undefined;
50
+
51
+ /**
52
+ * limit the size of the batch of uploaded files
53
+ *
54
+ * @default options.maxFileSize
55
+ */
56
+ maxTotalFileSize?: number | undefined;
57
+
58
+ /**
59
+ * limit the number of fields, set 0 for unlimited
60
+ *
61
+ * @default 1000
62
+ */
63
+ maxFields?: number | undefined;
64
+
65
+ /**
66
+ * limit the amount of memory all fields together (except files) can allocate in bytes
67
+ *
68
+ * @default 20 * 1024 * 1024
69
+ */
70
+ maxFieldsSize?: number | undefined;
71
+
72
+ /**
73
+ * include checksums calculated for incoming files, set this to some hash algorithm, see
74
+ * crypto.createHash for available algorithms
75
+ *
76
+ * @default false
77
+ */
78
+ hashAlgorithm?: string | false | undefined;
79
+
80
+ /**
81
+ * when you call the .parse method, the files argument (of the callback) will contain arrays of
82
+ * files for inputs which submit multiple files using the HTML5 multiple attribute. Also, the
83
+ * fields argument will contain arrays of values for fields that have names ending with '[]'
84
+ *
85
+ * @default false
86
+ */
87
+ multiples?: boolean | undefined;
88
+
89
+ enabledPlugins?: string[] | undefined;
90
+ }
91
+
92
+ export class HttpConfig {
93
+ debug: boolean = false;
94
+ parser: HttpParserOptions = {};
95
+ }