@opra/http 1.26.2 → 1.26.4
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/README.md +25 -2
- package/express-adapter.d.ts +15 -0
- package/express-adapter.js +19 -4
- package/http-adapter.d.ts +5 -3
- package/http-adapter.js +3 -1
- package/http-context.d.ts +18 -0
- package/http-context.js +20 -2
- package/http-handler.d.ts +32 -11
- package/http-handler.js +54 -33
- package/impl/local-file.d.ts +9 -0
- package/impl/local-file.js +9 -0
- package/impl/multipart-reader.d.ts +20 -0
- package/impl/multipart-reader.js +21 -1
- package/interfaces/http-incoming.interface.d.ts +13 -4
- package/interfaces/http-incoming.interface.js +7 -1
- package/interfaces/http-outgoing.interface.d.ts +12 -3
- package/interfaces/http-outgoing.interface.js +7 -1
- package/package.json +3 -3
- package/type-guards.d.ts +24 -0
- package/type-guards.js +24 -0
- package/utils/body-reader.d.ts +16 -2
- package/utils/body-reader.js +16 -2
- package/utils/common.d.ts +14 -6
- package/utils/common.js +14 -6
- package/utils/wrap-exception.d.ts +6 -0
- package/utils/wrap-exception.js +6 -0
package/README.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
-
# @opra/
|
|
1
|
+
# @opra/http
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[![NPM Version][npm-image]][npm-url]
|
|
4
|
+
[![NPM Downloads][downloads-image]][downloads-url]
|
|
5
|
+
[![CI Tests][ci-test-image]][ci-test-url]
|
|
6
|
+
[![Test Coverage][coveralls-image]][coveralls-url]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## Support
|
|
10
|
+
You can report bugs and discuss features on the [GitHub issues](https://github.com/panates/opra/issues) page.
|
|
11
|
+
|
|
12
|
+
## Node Compatibility
|
|
13
|
+
- node >= 20.x
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## License
|
|
17
|
+
Available under [MIT](LICENSE) license.
|
|
18
|
+
|
|
19
|
+
[npm-image]: https://img.shields.io/npm/v/@opra/http
|
|
20
|
+
[npm-url]: https://npmjs.org/package/@opra/http
|
|
21
|
+
[downloads-image]: https://img.shields.io/npm/dm/@opra/http.svg
|
|
22
|
+
[downloads-url]: https://npmjs.org/package/@opra/http
|
|
23
|
+
[ci-test-image]: https://github.com/panates/opra/actions/workflows/test.yml/badge.svg
|
|
24
|
+
[ci-test-url]: https://github.com/panates/opra/actions/workflows/test.yml
|
|
25
|
+
[coveralls-image]: https://coveralls.io/repos/github/panates/opra/badge.svg?branch=main
|
|
26
|
+
[coveralls-url]: https://coveralls.io/github/panates/opra?branch=main
|
package/express-adapter.d.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import { ApiDocument, HttpController } from '@opra/common';
|
|
2
2
|
import { type Application } from 'express';
|
|
3
3
|
import { HttpAdapter } from './http-adapter.js';
|
|
4
|
+
/**
|
|
5
|
+
* ExpressAdapter is a platform adapter for the Express.js framework.
|
|
6
|
+
* It integrates Opra with Express applications and routers.
|
|
7
|
+
*/
|
|
4
8
|
export declare class ExpressAdapter extends HttpAdapter {
|
|
5
9
|
readonly app: Application;
|
|
6
10
|
protected _controllerInstances: Map<HttpController, any>;
|
|
7
11
|
constructor(app: Application, document: ApiDocument, options?: HttpAdapter.Options);
|
|
8
12
|
get platform(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Closes the adapter and performs cleanup.
|
|
15
|
+
*
|
|
16
|
+
* @returns A promise that resolves when the adapter is closed.
|
|
17
|
+
*/
|
|
9
18
|
close(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Retrieves a controller instance by its path.
|
|
21
|
+
*
|
|
22
|
+
* @param controllerPath - The path of the controller.
|
|
23
|
+
* @returns The controller instance, or undefined if not found.
|
|
24
|
+
*/
|
|
10
25
|
getControllerInstance<T>(controllerPath: string): T | undefined;
|
|
11
26
|
protected _initRouter(): void;
|
|
12
27
|
protected _createControllers(controller: HttpController): void;
|
package/express-adapter.js
CHANGED
|
@@ -5,6 +5,10 @@ import { HttpAdapter } from './http-adapter.js';
|
|
|
5
5
|
import { HttpContext } from './http-context.js';
|
|
6
6
|
import { HttpIncoming } from './interfaces/http-incoming.interface.js';
|
|
7
7
|
import { HttpOutgoing } from './interfaces/http-outgoing.interface.js';
|
|
8
|
+
/**
|
|
9
|
+
* ExpressAdapter is a platform adapter for the Express.js framework.
|
|
10
|
+
* It integrates Opra with Express applications and routers.
|
|
11
|
+
*/
|
|
8
12
|
export class ExpressAdapter extends HttpAdapter {
|
|
9
13
|
app;
|
|
10
14
|
_controllerInstances = new Map();
|
|
@@ -21,6 +25,11 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
21
25
|
get platform() {
|
|
22
26
|
return 'express';
|
|
23
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Closes the adapter and performs cleanup.
|
|
30
|
+
*
|
|
31
|
+
* @returns A promise that resolves when the adapter is closed.
|
|
32
|
+
*/
|
|
24
33
|
async close() {
|
|
25
34
|
const processInstance = async (controller) => {
|
|
26
35
|
if (controller.controllers.size) {
|
|
@@ -35,6 +44,12 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
35
44
|
await processInstance(c);
|
|
36
45
|
this._controllerInstances.clear();
|
|
37
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Retrieves a controller instance by its path.
|
|
49
|
+
*
|
|
50
|
+
* @param controllerPath - The path of the controller.
|
|
51
|
+
* @returns The controller instance, or undefined if not found.
|
|
52
|
+
*/
|
|
38
53
|
getControllerInstance(controllerPath) {
|
|
39
54
|
const controller = this.api.findController(controllerPath);
|
|
40
55
|
return controller && this._controllerInstances.get(controller);
|
|
@@ -58,13 +73,13 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
58
73
|
await this.emitAsync('createContext', ctx);
|
|
59
74
|
return ctx;
|
|
60
75
|
};
|
|
61
|
-
|
|
76
|
+
/* Add an endpoint that returns document schema */
|
|
62
77
|
router.get('/\\$schema', (_req, _res, next) => {
|
|
63
78
|
createContext(_req, _res)
|
|
64
79
|
.then(ctx => this.handler.sendDocumentSchema(ctx).catch(next))
|
|
65
80
|
.catch(next);
|
|
66
81
|
});
|
|
67
|
-
|
|
82
|
+
/* Add operation endpoints */
|
|
68
83
|
if (this.api.controllers.size) {
|
|
69
84
|
const processResource = (controller, currentPath) => {
|
|
70
85
|
currentPath = nodePath.posix.join(currentPath, controller.path);
|
|
@@ -74,7 +89,7 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
74
89
|
const operationHandler = controllerInstance[operation.name];
|
|
75
90
|
if (!operationHandler)
|
|
76
91
|
continue;
|
|
77
|
-
|
|
92
|
+
/* Define router callback */
|
|
78
93
|
router[operation.method.toLowerCase()](routePath, (_req, _res, _next) => {
|
|
79
94
|
createContext(_req, _res, {
|
|
80
95
|
controller,
|
|
@@ -98,7 +113,7 @@ export class ExpressAdapter extends HttpAdapter {
|
|
|
98
113
|
for (const c of this.api.controllers.values())
|
|
99
114
|
processResource(c, '/');
|
|
100
115
|
}
|
|
101
|
-
|
|
116
|
+
/* Add an endpoint that returns 404 error at last */
|
|
102
117
|
router.use('/{*splat}', (_req, _res, next) => {
|
|
103
118
|
createContext(_req, _res)
|
|
104
119
|
.then(ctx => {
|
package/http-adapter.d.ts
CHANGED
|
@@ -4,8 +4,10 @@ import { EventMap } from 'node-events-async';
|
|
|
4
4
|
import { HttpContext } from './http-context.js';
|
|
5
5
|
import { HttpHandler } from './http-handler.js';
|
|
6
6
|
/**
|
|
7
|
+
* HttpAdapter is the base class for all HTTP platform adapters.
|
|
8
|
+
* It provides core functionality for handling HTTP requests and managing interceptors.
|
|
7
9
|
*
|
|
8
|
-
* @
|
|
10
|
+
* @abstract
|
|
9
11
|
*/
|
|
10
12
|
export declare abstract class HttpAdapter<T extends HttpAdapter.Events = HttpAdapter.Events> extends PlatformAdapter<EventMap<T>> {
|
|
11
13
|
readonly handler: HttpHandler;
|
|
@@ -19,11 +21,11 @@ export declare abstract class HttpAdapter<T extends HttpAdapter.Events = HttpAda
|
|
|
19
21
|
export declare namespace HttpAdapter {
|
|
20
22
|
type NextCallback = () => Promise<void>;
|
|
21
23
|
/**
|
|
22
|
-
*
|
|
24
|
+
* The interceptor function signature.
|
|
23
25
|
*/
|
|
24
26
|
type InterceptorFunction = IHttpInterceptor['intercept'];
|
|
25
27
|
/**
|
|
26
|
-
*
|
|
28
|
+
* Interface for HTTP interceptors.
|
|
27
29
|
*/
|
|
28
30
|
type IHttpInterceptor = {
|
|
29
31
|
intercept(context: HttpContext, next: NextCallback): Promise<void>;
|
package/http-adapter.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { PlatformAdapter } from '@opra/core';
|
|
2
2
|
import { HttpHandler } from './http-handler.js';
|
|
3
3
|
/**
|
|
4
|
+
* HttpAdapter is the base class for all HTTP platform adapters.
|
|
5
|
+
* It provides core functionality for handling HTTP requests and managing interceptors.
|
|
4
6
|
*
|
|
5
|
-
* @
|
|
7
|
+
* @abstract
|
|
6
8
|
*/
|
|
7
9
|
export class HttpAdapter extends PlatformAdapter {
|
|
8
10
|
handler;
|
package/http-context.d.ts
CHANGED
|
@@ -21,8 +21,26 @@ export declare class HttpContext extends ExecutionContext {
|
|
|
21
21
|
readonly queryParams: Record<string, any>;
|
|
22
22
|
errors: Error[];
|
|
23
23
|
constructor(init: HttpContext.Initiator);
|
|
24
|
+
/**
|
|
25
|
+
* Checks if the request is a multipart request.
|
|
26
|
+
*/
|
|
24
27
|
get isMultipart(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Retrieves the multipart reader for the current context.
|
|
30
|
+
*
|
|
31
|
+
* @returns A promise that resolves to the multipart reader.
|
|
32
|
+
* @throws {@link InternalServerError} If the request content is not multipart.
|
|
33
|
+
* @throws {@link NotAcceptableError} If the endpoint does not accept multipart requests.
|
|
34
|
+
*/
|
|
25
35
|
getMultipartReader(): Promise<MultipartReader>;
|
|
36
|
+
/**
|
|
37
|
+
* Retrieves and parses the request body.
|
|
38
|
+
*
|
|
39
|
+
* @param args - Optional arguments for body retrieval.
|
|
40
|
+
* @param args.toFile - Whether to save the body to a file.
|
|
41
|
+
* @returns A promise that resolves to the parsed body.
|
|
42
|
+
* @throws {@link BadRequestError} If the body cannot be parsed or validated.
|
|
43
|
+
*/
|
|
26
44
|
getBody<T>(args?: {
|
|
27
45
|
toFile: boolean | string;
|
|
28
46
|
}): Promise<T>;
|
package/http-context.js
CHANGED
|
@@ -44,9 +44,19 @@ export class HttpContext extends ExecutionContext {
|
|
|
44
44
|
this._multipartReader.purge().catch(() => undefined);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Checks if the request is a multipart request.
|
|
49
|
+
*/
|
|
47
50
|
get isMultipart() {
|
|
48
51
|
return !!this.request.is('multipart');
|
|
49
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Retrieves the multipart reader for the current context.
|
|
55
|
+
*
|
|
56
|
+
* @returns A promise that resolves to the multipart reader.
|
|
57
|
+
* @throws {@link InternalServerError} If the request content is not multipart.
|
|
58
|
+
* @throws {@link NotAcceptableError} If the endpoint does not accept multipart requests.
|
|
59
|
+
*/
|
|
50
60
|
async getMultipartReader() {
|
|
51
61
|
if (!this.isMultipart)
|
|
52
62
|
throw new InternalServerError('Request content is not a multipart content');
|
|
@@ -72,6 +82,14 @@ export class HttpContext extends ExecutionContext {
|
|
|
72
82
|
this._multipartReader = reader;
|
|
73
83
|
return reader;
|
|
74
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Retrieves and parses the request body.
|
|
87
|
+
*
|
|
88
|
+
* @param args - Optional arguments for body retrieval.
|
|
89
|
+
* @param args.toFile - Whether to save the body to a file.
|
|
90
|
+
* @returns A promise that resolves to the parsed body.
|
|
91
|
+
* @throws {@link BadRequestError} If the body cannot be parsed or validated.
|
|
92
|
+
*/
|
|
75
93
|
async getBody(args) {
|
|
76
94
|
if (this._body !== undefined)
|
|
77
95
|
return this._body;
|
|
@@ -79,9 +97,9 @@ export class HttpContext extends ExecutionContext {
|
|
|
79
97
|
const { request, __oprDef, mediaType } = this;
|
|
80
98
|
if (this.isMultipart) {
|
|
81
99
|
const reader = await this.getMultipartReader();
|
|
82
|
-
|
|
100
|
+
/* Retrieve all fields */
|
|
83
101
|
const parts = await reader.getAll();
|
|
84
|
-
|
|
102
|
+
/* Filter fields according to configuration */
|
|
85
103
|
this._body = [...parts];
|
|
86
104
|
return this._body;
|
|
87
105
|
}
|
package/http-handler.d.ts
CHANGED
|
@@ -18,7 +18,8 @@ export declare namespace HttpHandler {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* HttpHandler is responsible for processing incoming HTTP requests.
|
|
22
|
+
* It handles request parsing, interceptor execution, and response generation.
|
|
22
23
|
*/
|
|
23
24
|
export declare class HttpHandler {
|
|
24
25
|
readonly adapter: HttpAdapter;
|
|
@@ -26,25 +27,35 @@ export declare class HttpHandler {
|
|
|
26
27
|
onError?: (context: HttpContext, error: OpraException) => void | Promise<void>;
|
|
27
28
|
constructor(adapter: HttpAdapter);
|
|
28
29
|
/**
|
|
29
|
-
* Main
|
|
30
|
-
*
|
|
30
|
+
* Main HTTP request handler.
|
|
31
|
+
*
|
|
32
|
+
* @param context - The HTTP execution context.
|
|
33
|
+
* @returns A promise that resolves when the request is handled.
|
|
31
34
|
* @protected
|
|
32
35
|
*/
|
|
33
36
|
handleRequest(context: HttpContext): Promise<void>;
|
|
34
37
|
/**
|
|
38
|
+
* Parses the HTTP request, including parameters and content type.
|
|
35
39
|
*
|
|
36
|
-
* @param context
|
|
40
|
+
* @param context - The HTTP execution context.
|
|
41
|
+
* @returns A promise that resolves when the request is parsed.
|
|
37
42
|
*/
|
|
38
43
|
parseRequest(context: HttpContext): Promise<void>;
|
|
39
44
|
/**
|
|
45
|
+
* Parses various HTTP parameters (cookies, headers, path, query).
|
|
40
46
|
*
|
|
41
|
-
* @param context
|
|
47
|
+
* @param context - The HTTP execution context.
|
|
48
|
+
* @returns A promise that resolves when parameters are parsed.
|
|
49
|
+
* @throws {@link BadRequestError} If parameter validation fails.
|
|
42
50
|
* @protected
|
|
43
51
|
*/
|
|
44
52
|
protected _parseParameters(context: HttpContext): Promise<void>;
|
|
45
53
|
/**
|
|
54
|
+
* Parses and validates the request content type.
|
|
46
55
|
*
|
|
47
|
-
* @param context
|
|
56
|
+
* @param context - The HTTP execution context.
|
|
57
|
+
* @returns A promise that resolves when content type is parsed.
|
|
58
|
+
* @throws {@link BadRequestError} If the content type is invalid or missing.
|
|
48
59
|
* @protected
|
|
49
60
|
*/
|
|
50
61
|
protected _parseContentType(context: HttpContext): Promise<void>;
|
|
@@ -55,18 +66,28 @@ export declare class HttpHandler {
|
|
|
55
66
|
*/
|
|
56
67
|
protected _executeRequest(context: HttpContext): Promise<any>;
|
|
57
68
|
/**
|
|
69
|
+
* Sends an HTTP response back to the client.
|
|
58
70
|
*
|
|
59
|
-
* @param context
|
|
60
|
-
* @param responseValue
|
|
61
|
-
* @
|
|
71
|
+
* @param context - The HTTP execution context.
|
|
72
|
+
* @param responseValue - The value to be sent in the response body.
|
|
73
|
+
* @returns A promise that resolves when the response is sent.
|
|
62
74
|
*/
|
|
63
75
|
sendResponse(context: HttpContext, responseValue?: any): Promise<void>;
|
|
64
76
|
protected _sendErrorResponse(context: HttpContext): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Sends the document schema as a JSON response.
|
|
79
|
+
*
|
|
80
|
+
* @param context - The HTTP execution context.
|
|
81
|
+
* @returns A promise that resolves when the schema is sent.
|
|
82
|
+
*/
|
|
65
83
|
sendDocumentSchema(context: HttpContext): Promise<void>;
|
|
66
84
|
/**
|
|
85
|
+
* Determines the response arguments (status code, content type, etc.) for a given response value.
|
|
67
86
|
*
|
|
68
|
-
* @param context
|
|
69
|
-
* @param body
|
|
87
|
+
* @param context - The HTTP execution context.
|
|
88
|
+
* @param body - The response body.
|
|
89
|
+
* @returns The determined response arguments.
|
|
90
|
+
* @throws {@link InternalServerError} If response configuration is missing or invalid.
|
|
70
91
|
* @protected
|
|
71
92
|
*/
|
|
72
93
|
protected _determineResponseArgs(context: HttpContext, body: any): HttpHandler.ResponseArgs;
|
package/http-handler.js
CHANGED
|
@@ -9,7 +9,8 @@ import { asMutable } from 'ts-gems';
|
|
|
9
9
|
import { toArray, ValidationError, vg, } from 'valgen';
|
|
10
10
|
import { wrapException } from './utils/wrap-exception.js';
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* HttpHandler is responsible for processing incoming HTTP requests.
|
|
13
|
+
* It handles request parsing, interceptor execution, and response generation.
|
|
13
14
|
*/
|
|
14
15
|
export class HttpHandler {
|
|
15
16
|
adapter;
|
|
@@ -20,8 +21,10 @@ export class HttpHandler {
|
|
|
20
21
|
this[kAssetCache] = adapter[kAssetCache];
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
|
-
* Main
|
|
24
|
-
*
|
|
24
|
+
* Main HTTP request handler.
|
|
25
|
+
*
|
|
26
|
+
* @param context - The HTTP execution context.
|
|
27
|
+
* @returns A promise that resolves when the request is handled.
|
|
25
28
|
* @protected
|
|
26
29
|
*/
|
|
27
30
|
async handleRequest(context) {
|
|
@@ -88,15 +91,17 @@ export class HttpHandler {
|
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
/**
|
|
94
|
+
* Parses the HTTP request, including parameters and content type.
|
|
91
95
|
*
|
|
92
|
-
* @param context
|
|
96
|
+
* @param context - The HTTP execution context.
|
|
97
|
+
* @returns A promise that resolves when the request is parsed.
|
|
93
98
|
*/
|
|
94
99
|
async parseRequest(context) {
|
|
95
100
|
await this._parseParameters(context);
|
|
96
101
|
await this._parseContentType(context);
|
|
97
102
|
if (context.__oprDef?.requestBody?.immediateFetch)
|
|
98
103
|
await context.getBody();
|
|
99
|
-
|
|
104
|
+
/* Set default status code as the first status code between 200 and 299 */
|
|
100
105
|
if (context.__oprDef) {
|
|
101
106
|
for (const r of context.__oprDef.responses) {
|
|
102
107
|
const st = r.statusCode.find(sc => sc.start <= 299 && sc.end >= 200);
|
|
@@ -108,8 +113,11 @@ export class HttpHandler {
|
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
/**
|
|
116
|
+
* Parses various HTTP parameters (cookies, headers, path, query).
|
|
111
117
|
*
|
|
112
|
-
* @param context
|
|
118
|
+
* @param context - The HTTP execution context.
|
|
119
|
+
* @returns A promise that resolves when parameters are parsed.
|
|
120
|
+
* @throws {@link BadRequestError} If parameter validation fails.
|
|
113
121
|
* @protected
|
|
114
122
|
*/
|
|
115
123
|
async _parseParameters(context) {
|
|
@@ -122,7 +130,7 @@ export class HttpHandler {
|
|
|
122
130
|
issue.location = key;
|
|
123
131
|
return issue;
|
|
124
132
|
};
|
|
125
|
-
|
|
133
|
+
/* prepare decoders */
|
|
126
134
|
const getDecoder = (prm) => {
|
|
127
135
|
let decode = this[kAssetCache].get(prm, 'decode');
|
|
128
136
|
if (!decode) {
|
|
@@ -138,7 +146,7 @@ export class HttpHandler {
|
|
|
138
146
|
...__oprDef.parameters,
|
|
139
147
|
...__oprDef.owner.parameters,
|
|
140
148
|
]);
|
|
141
|
-
|
|
149
|
+
/* parse cookie parameters */
|
|
142
150
|
if (request.cookies) {
|
|
143
151
|
for (key of Object.keys(request.cookies)) {
|
|
144
152
|
const oprPrm = __oprDef.findParameter(key, 'cookie');
|
|
@@ -161,7 +169,7 @@ export class HttpHandler {
|
|
|
161
169
|
context.cookies[prmName] = v;
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
|
-
|
|
172
|
+
/* parse headers */
|
|
165
173
|
if (request.headers) {
|
|
166
174
|
for (key of Object.keys(request.headers)) {
|
|
167
175
|
const oprPrm = __oprDef.findParameter(key, 'header');
|
|
@@ -184,7 +192,7 @@ export class HttpHandler {
|
|
|
184
192
|
context.headers[prmName] = v;
|
|
185
193
|
}
|
|
186
194
|
}
|
|
187
|
-
|
|
195
|
+
/* parse path parameters */
|
|
188
196
|
if (request.params) {
|
|
189
197
|
for (key of Object.keys(request.params)) {
|
|
190
198
|
const oprPrm = __oprDef.findParameter(key, 'path');
|
|
@@ -206,7 +214,7 @@ export class HttpHandler {
|
|
|
206
214
|
context.pathParams[key] = v;
|
|
207
215
|
}
|
|
208
216
|
}
|
|
209
|
-
|
|
217
|
+
/* parse query parameters */
|
|
210
218
|
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
211
219
|
const { searchParams } = url;
|
|
212
220
|
for (key of searchParams.keys()) {
|
|
@@ -267,8 +275,11 @@ export class HttpHandler {
|
|
|
267
275
|
}
|
|
268
276
|
}
|
|
269
277
|
/**
|
|
278
|
+
* Parses and validates the request content type.
|
|
270
279
|
*
|
|
271
|
-
* @param context
|
|
280
|
+
* @param context - The HTTP execution context.
|
|
281
|
+
* @returns A promise that resolves when content type is parsed.
|
|
282
|
+
* @throws {@link BadRequestError} If the content type is invalid or missing.
|
|
272
283
|
* @protected
|
|
273
284
|
*/
|
|
274
285
|
async _parseContentType(context) {
|
|
@@ -318,10 +329,11 @@ export class HttpHandler {
|
|
|
318
329
|
}
|
|
319
330
|
}
|
|
320
331
|
/**
|
|
332
|
+
* Sends an HTTP response back to the client.
|
|
321
333
|
*
|
|
322
|
-
* @param context
|
|
323
|
-
* @param responseValue
|
|
324
|
-
* @
|
|
334
|
+
* @param context - The HTTP execution context.
|
|
335
|
+
* @param responseValue - The value to be sent in the response body.
|
|
336
|
+
* @returns A promise that resolves when the response is sent.
|
|
325
337
|
*/
|
|
326
338
|
async sendResponse(context, responseValue) {
|
|
327
339
|
if (context.errors.length)
|
|
@@ -341,11 +353,11 @@ export class HttpHandler {
|
|
|
341
353
|
});
|
|
342
354
|
this[kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
|
|
343
355
|
}
|
|
344
|
-
|
|
356
|
+
/* Validate response */
|
|
345
357
|
if (operationResponse?.type) {
|
|
346
358
|
if (!(body == null &&
|
|
347
359
|
statusCode === HttpStatusCode.NO_CONTENT)) {
|
|
348
|
-
|
|
360
|
+
/* Generate encoder */
|
|
349
361
|
const projection = responseArgs.projection || '*';
|
|
350
362
|
const assetKey = md5(String(projection));
|
|
351
363
|
let encode = this[kAssetCache].get(operationResponse, 'encode:' + assetKey);
|
|
@@ -363,7 +375,7 @@ export class HttpHandler {
|
|
|
363
375
|
this[kAssetCache].set(operationResponse, 'encode:' + assetKey, encode);
|
|
364
376
|
}
|
|
365
377
|
}
|
|
366
|
-
|
|
378
|
+
/* Encode body */
|
|
367
379
|
if (operationResponse.type.extendsFrom(operationResultType)) {
|
|
368
380
|
if (body instanceof OperationResult)
|
|
369
381
|
body = encode(body);
|
|
@@ -410,7 +422,7 @@ export class HttpHandler {
|
|
|
410
422
|
body = String(body);
|
|
411
423
|
}
|
|
412
424
|
}
|
|
413
|
-
|
|
425
|
+
/* Set content-type header value if not set */
|
|
414
426
|
if (contentType && contentType !== responseArgs.contentType)
|
|
415
427
|
response.setHeader('content-type', contentType);
|
|
416
428
|
response.status(statusCode);
|
|
@@ -497,6 +509,12 @@ export class HttpHandler {
|
|
|
497
509
|
response.send(safeJsonStringify(body));
|
|
498
510
|
response.end();
|
|
499
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Sends the document schema as a JSON response.
|
|
514
|
+
*
|
|
515
|
+
* @param context - The HTTP execution context.
|
|
516
|
+
* @returns A promise that resolves when the schema is sent.
|
|
517
|
+
*/
|
|
500
518
|
async sendDocumentSchema(context) {
|
|
501
519
|
const { request, response } = context;
|
|
502
520
|
const { document } = this.adapter;
|
|
@@ -511,9 +529,9 @@ export class HttpHandler {
|
|
|
511
529
|
}));
|
|
512
530
|
return this.sendResponse(context);
|
|
513
531
|
}
|
|
514
|
-
|
|
532
|
+
/* Check if response cache exists */
|
|
515
533
|
let responseBody = this[kAssetCache].get(doc, `$schema`);
|
|
516
|
-
|
|
534
|
+
/* Create response if response cache does not exists */
|
|
517
535
|
if (!responseBody) {
|
|
518
536
|
const schema = doc.export({
|
|
519
537
|
scope: this.adapter.scope,
|
|
@@ -524,9 +542,12 @@ export class HttpHandler {
|
|
|
524
542
|
response.end(responseBody);
|
|
525
543
|
}
|
|
526
544
|
/**
|
|
545
|
+
* Determines the response arguments (status code, content type, etc.) for a given response value.
|
|
527
546
|
*
|
|
528
|
-
* @param context
|
|
529
|
-
* @param body
|
|
547
|
+
* @param context - The HTTP execution context.
|
|
548
|
+
* @param body - The response body.
|
|
549
|
+
* @returns The determined response arguments.
|
|
550
|
+
* @throws {@link InternalServerError} If response configuration is missing or invalid.
|
|
530
551
|
* @protected
|
|
531
552
|
*/
|
|
532
553
|
_determineResponseArgs(context, body) {
|
|
@@ -535,12 +556,12 @@ export class HttpHandler {
|
|
|
535
556
|
const statusCode = !hasBody && response.statusCode === HttpStatusCode.OK
|
|
536
557
|
? HttpStatusCode.NO_CONTENT
|
|
537
558
|
: response.statusCode;
|
|
538
|
-
|
|
559
|
+
/* Parse content-type header */
|
|
539
560
|
const parsedContentType = hasBody && response.hasHeader('content-type')
|
|
540
561
|
? parseContentType(response)
|
|
541
562
|
: undefined;
|
|
542
563
|
let contentType = parsedContentType?.type;
|
|
543
|
-
|
|
564
|
+
/* Estimate content type if not defined */
|
|
544
565
|
if (hasBody && !contentType) {
|
|
545
566
|
if (body instanceof OperationResult)
|
|
546
567
|
contentType = MimeTypes.opra_response_json;
|
|
@@ -553,21 +574,21 @@ export class HttpHandler {
|
|
|
553
574
|
if (!responseArgs) {
|
|
554
575
|
responseArgs = { statusCode, contentType };
|
|
555
576
|
if (__oprDef?.responses.length) {
|
|
556
|
-
|
|
577
|
+
/* Filter available HttpOperationResponse instances according to status code. */
|
|
557
578
|
const filteredResponses = __oprDef.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
|
|
558
|
-
|
|
579
|
+
/* Throw InternalServerError if controller returns non-configured status code */
|
|
559
580
|
if (!filteredResponses.length && statusCode < 400) {
|
|
560
581
|
throw new InternalServerError(`No responses defined for status code ${statusCode} in operation "${__oprDef.name}"`);
|
|
561
582
|
}
|
|
562
|
-
|
|
583
|
+
/* We search for content-type in filtered HttpOperationResponse array */
|
|
563
584
|
if (filteredResponses.length) {
|
|
564
|
-
|
|
585
|
+
/* If no response returned, and content-type has not been set (No response wants to be returned by operation) */
|
|
565
586
|
if (!hasBody) {
|
|
566
|
-
|
|
587
|
+
/* Find HttpOperationResponse with no content-type */
|
|
567
588
|
operationResponse = filteredResponses.find(r => !r.contentType);
|
|
568
589
|
}
|
|
569
590
|
if (!operationResponse) {
|
|
570
|
-
|
|
591
|
+
/* Find HttpOperationResponse according to content-type */
|
|
571
592
|
if (contentType) {
|
|
572
593
|
// Find HttpEndpointResponse instance according to content-type header
|
|
573
594
|
operationResponse = filteredResponses.find(r => typeIs.is(contentType, toArray(r.contentType)));
|
|
@@ -576,7 +597,7 @@ export class HttpHandler {
|
|
|
576
597
|
}
|
|
577
598
|
}
|
|
578
599
|
else {
|
|
579
|
-
|
|
600
|
+
/* Select first HttpOperationResponse if content-type header has not been set */
|
|
580
601
|
operationResponse = filteredResponses[0];
|
|
581
602
|
if (operationResponse.contentType) {
|
|
582
603
|
const ct = typeIs.normalize(Array.isArray(operationResponse.contentType)
|
|
@@ -603,7 +624,7 @@ export class HttpHandler {
|
|
|
603
624
|
}
|
|
604
625
|
this[kAssetCache].set(response, cacheKey, { ...responseArgs });
|
|
605
626
|
}
|
|
606
|
-
|
|
627
|
+
/* Fix response value according to composition */
|
|
607
628
|
const composition = operationResponse?.owner.composition;
|
|
608
629
|
if (composition && body != null) {
|
|
609
630
|
switch (composition) {
|
package/impl/local-file.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalFile represents a file stored on the local file system.
|
|
3
|
+
* It provides utility methods for reading the file content and managing its lifecycle.
|
|
4
|
+
*/
|
|
1
5
|
export declare class LocalFile {
|
|
2
6
|
private _autoDelete;
|
|
3
7
|
readonly storedPath: string;
|
|
@@ -7,6 +11,11 @@ export declare class LocalFile {
|
|
|
7
11
|
constructor(storedPath: string, options?: LocalFile.Options);
|
|
8
12
|
text(): Promise<string>;
|
|
9
13
|
buffer(): Promise<Buffer>;
|
|
14
|
+
/**
|
|
15
|
+
* Deletes the local file.
|
|
16
|
+
*
|
|
17
|
+
* @returns A promise that resolves when the file is deleted.
|
|
18
|
+
*/
|
|
10
19
|
delete(): Promise<void>;
|
|
11
20
|
get size(): number;
|
|
12
21
|
get autoDelete(): boolean;
|
package/impl/local-file.js
CHANGED
|
@@ -6,6 +6,10 @@ import { uid } from 'uid';
|
|
|
6
6
|
const registry = new FinalizationRegistry((storedPath) => {
|
|
7
7
|
fs.unlink(storedPath, () => undefined);
|
|
8
8
|
});
|
|
9
|
+
/**
|
|
10
|
+
* LocalFile represents a file stored on the local file system.
|
|
11
|
+
* It provides utility methods for reading the file content and managing its lifecycle.
|
|
12
|
+
*/
|
|
9
13
|
export class LocalFile {
|
|
10
14
|
_autoDelete = false;
|
|
11
15
|
storedPath;
|
|
@@ -25,6 +29,11 @@ export class LocalFile {
|
|
|
25
29
|
async buffer() {
|
|
26
30
|
return fsAsync.readFile(this.storedPath);
|
|
27
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Deletes the local file.
|
|
34
|
+
*
|
|
35
|
+
* @returns A promise that resolves when the file is deleted.
|
|
36
|
+
*/
|
|
28
37
|
async delete() {
|
|
29
38
|
if (fs.existsSync(this.storedPath)) {
|
|
30
39
|
try {
|
|
@@ -4,6 +4,10 @@ import { EventEmitter } from 'events';
|
|
|
4
4
|
import type { StrictOmit } from 'ts-gems';
|
|
5
5
|
import type { HttpContext } from '../http-context.js';
|
|
6
6
|
import { LocalFile } from './local-file.js';
|
|
7
|
+
/**
|
|
8
|
+
* MultipartReader is responsible for parsing multipart/form-data requests.
|
|
9
|
+
* It uses busboy to stream incoming parts and can handle both fields and files.
|
|
10
|
+
*/
|
|
7
11
|
export declare class MultipartReader extends EventEmitter {
|
|
8
12
|
protected context: HttpContext;
|
|
9
13
|
protected mediaType?: HttpMediaType | undefined;
|
|
@@ -17,12 +21,28 @@ export declare class MultipartReader extends EventEmitter {
|
|
|
17
21
|
scope?: string;
|
|
18
22
|
constructor(context: HttpContext, options?: MultipartReader.Options, mediaType?: HttpMediaType | undefined);
|
|
19
23
|
get items(): MultipartReader.Item[];
|
|
24
|
+
/**
|
|
25
|
+
* Retrieves the next item (field or file) from the multipart stream.
|
|
26
|
+
*
|
|
27
|
+
* @returns A promise that resolves to the next item, or undefined if no more items are available.
|
|
28
|
+
* @throws {@link BadRequestError} If a field is unknown or validation fails.
|
|
29
|
+
*/
|
|
20
30
|
getNext(): Promise<MultipartReader.Item | undefined>;
|
|
31
|
+
/**
|
|
32
|
+
* Retrieves all items from the multipart stream.
|
|
33
|
+
*
|
|
34
|
+
* @returns A promise that resolves to an array of all items.
|
|
35
|
+
*/
|
|
21
36
|
getAll(): Promise<MultipartReader.Item[]>;
|
|
22
37
|
getAll_(): Promise<MultipartReader.Item[]>;
|
|
23
38
|
cancel(): void;
|
|
24
39
|
resume(): void;
|
|
25
40
|
pause(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Purges all temporary files created by the reader.
|
|
43
|
+
*
|
|
44
|
+
* @returns A promise that resolves when all files are purged.
|
|
45
|
+
*/
|
|
26
46
|
purge(): Promise<PromiseSettledResult<any>[]>;
|
|
27
47
|
}
|
|
28
48
|
export declare class MultipartFile extends LocalFile {
|
package/impl/multipart-reader.js
CHANGED
|
@@ -7,6 +7,10 @@ import { EventEmitter } from 'events';
|
|
|
7
7
|
import fsPromise from 'fs/promises';
|
|
8
8
|
import { isNotNullish } from 'valgen';
|
|
9
9
|
import { LocalFile } from './local-file.js';
|
|
10
|
+
/**
|
|
11
|
+
* MultipartReader is responsible for parsing multipart/form-data requests.
|
|
12
|
+
* It uses busboy to stream incoming parts and can handle both fields and files.
|
|
13
|
+
*/
|
|
10
14
|
export class MultipartReader extends EventEmitter {
|
|
11
15
|
context;
|
|
12
16
|
mediaType;
|
|
@@ -70,6 +74,12 @@ export class MultipartReader extends EventEmitter {
|
|
|
70
74
|
get items() {
|
|
71
75
|
return this._items;
|
|
72
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Retrieves the next item (field or file) from the multipart stream.
|
|
79
|
+
*
|
|
80
|
+
* @returns A promise that resolves to the next item, or undefined if no more items are available.
|
|
81
|
+
* @throws {@link BadRequestError} If a field is unknown or validation fails.
|
|
82
|
+
*/
|
|
73
83
|
async getNext() {
|
|
74
84
|
let item = this._stack.shift();
|
|
75
85
|
if (!item && !this._finished) {
|
|
@@ -122,7 +132,7 @@ export class MultipartReader extends EventEmitter {
|
|
|
122
132
|
}
|
|
123
133
|
}
|
|
124
134
|
}
|
|
125
|
-
|
|
135
|
+
/* if all items received we check for required items */
|
|
126
136
|
if (this._finished &&
|
|
127
137
|
this.mediaType &&
|
|
128
138
|
this.mediaType.multipartFields?.length > 0) {
|
|
@@ -155,6 +165,11 @@ export class MultipartReader extends EventEmitter {
|
|
|
155
165
|
}
|
|
156
166
|
return item;
|
|
157
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Retrieves all items from the multipart stream.
|
|
170
|
+
*
|
|
171
|
+
* @returns A promise that resolves to an array of all items.
|
|
172
|
+
*/
|
|
158
173
|
async getAll() {
|
|
159
174
|
const items = [...this._items];
|
|
160
175
|
let item;
|
|
@@ -189,6 +204,11 @@ export class MultipartReader extends EventEmitter {
|
|
|
189
204
|
pause() {
|
|
190
205
|
this.context.request.pause();
|
|
191
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Purges all temporary files created by the reader.
|
|
209
|
+
*
|
|
210
|
+
* @returns A promise that resolves when all files are purged.
|
|
211
|
+
*/
|
|
192
212
|
async purge() {
|
|
193
213
|
const promises = [];
|
|
194
214
|
this._items.forEach(item => {
|
|
@@ -3,7 +3,8 @@ import { BodyReader } from '../utils/body-reader.js';
|
|
|
3
3
|
import type { HttpOutgoing } from './http-outgoing.interface.js';
|
|
4
4
|
import { NodeIncomingMessage } from './node-incoming-message.interface.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* HttpIncoming represents an incoming HTTP request.
|
|
7
|
+
* It extends NodeIncomingMessage with additional functionality for handling HTTP requests.
|
|
7
8
|
*/
|
|
8
9
|
export interface HttpIncoming extends NodeIncomingMessage {
|
|
9
10
|
res: HttpOutgoing;
|
|
@@ -180,14 +181,22 @@ export interface HttpIncoming extends NodeIncomingMessage {
|
|
|
180
181
|
*/
|
|
181
182
|
range(size: number, options?: RangeParserOptions): RangeParserRanges | RangeParserResult | undefined;
|
|
182
183
|
/**
|
|
183
|
-
* Receives the body
|
|
184
|
-
*
|
|
184
|
+
* Receives and parses the request body.
|
|
185
|
+
*
|
|
186
|
+
* @param options - Optional reader settings.
|
|
187
|
+
* @returns A promise that resolves to the body content.
|
|
185
188
|
*/
|
|
186
189
|
readBody(options?: BodyReader.Options): Promise<string | Buffer | undefined>;
|
|
187
190
|
}
|
|
188
191
|
/**
|
|
189
|
-
*
|
|
192
|
+
* Utility functions for HttpIncoming.
|
|
190
193
|
*/
|
|
191
194
|
export declare namespace HttpIncoming {
|
|
195
|
+
/**
|
|
196
|
+
* Creates an HttpIncoming instance from various sources.
|
|
197
|
+
*
|
|
198
|
+
* @param instance - The source instance.
|
|
199
|
+
* @returns The HttpIncoming instance.
|
|
200
|
+
*/
|
|
192
201
|
function from(instance: HttpIncoming | NodeIncomingMessage.Initiator | string | Iterable<any> | AsyncIterable<any>): HttpIncoming;
|
|
193
202
|
}
|
|
@@ -3,10 +3,16 @@ import { HttpIncomingHost } from '../impl/http-incoming.host.js';
|
|
|
3
3
|
import { isHttpIncoming, isNodeIncomingMessage } from '../type-guards.js';
|
|
4
4
|
import { NodeIncomingMessage } from './node-incoming-message.interface.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Utility functions for HttpIncoming.
|
|
7
7
|
*/
|
|
8
8
|
export var HttpIncoming;
|
|
9
9
|
(function (HttpIncoming) {
|
|
10
|
+
/**
|
|
11
|
+
* Creates an HttpIncoming instance from various sources.
|
|
12
|
+
*
|
|
13
|
+
* @param instance - The source instance.
|
|
14
|
+
* @returns The HttpIncoming instance.
|
|
15
|
+
*/
|
|
10
16
|
function from(instance) {
|
|
11
17
|
if (isHttpIncoming(instance))
|
|
12
18
|
return instance;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type StrictOmit } from 'ts-gems';
|
|
2
2
|
import type { HttpIncoming } from './http-incoming.interface.js';
|
|
3
3
|
import { NodeOutgoingMessage } from './node-outgoing-message.interface.js';
|
|
4
|
+
/**
|
|
5
|
+
* HttpOutgoing represents an outgoing HTTP response.
|
|
6
|
+
* It extends NodeOutgoingMessage with additional functionality for handling HTTP responses.
|
|
7
|
+
*/
|
|
4
8
|
export interface HttpOutgoing extends StrictOmit<NodeOutgoingMessage, 'req' | 'appendHeader' | 'setHeader'> {
|
|
5
9
|
req: HttpIncoming;
|
|
6
10
|
readonly finished?: boolean;
|
|
@@ -10,7 +14,6 @@ export interface HttpOutgoing extends StrictOmit<NodeOutgoingMessage, 'req' | 'a
|
|
|
10
14
|
* Set _Content-Disposition_ header to _attachment_ with optional `filename`.
|
|
11
15
|
*/
|
|
12
16
|
attachment(filename?: string): this;
|
|
13
|
-
/** Clear cookie `name`. */
|
|
14
17
|
clearCookie(name: string, options?: CookieOptions): this;
|
|
15
18
|
/**
|
|
16
19
|
* Set cookie `name` to `val`, with the given `options`.
|
|
@@ -106,7 +109,7 @@ export interface HttpOutgoing extends StrictOmit<NodeOutgoingMessage, 'req' | 'a
|
|
|
106
109
|
*/
|
|
107
110
|
status(code: number): this;
|
|
108
111
|
/**
|
|
109
|
-
* Send given HTTP status code.
|
|
112
|
+
* Send the given HTTP status code.
|
|
110
113
|
*
|
|
111
114
|
* Sets the response status to `statusCode` and the body of the
|
|
112
115
|
* response to the standard description from node's http.STATUS_CODES
|
|
@@ -137,8 +140,14 @@ export interface CookieOptions {
|
|
|
137
140
|
sameSite?: boolean | 'lax' | 'strict' | 'none';
|
|
138
141
|
}
|
|
139
142
|
/**
|
|
140
|
-
*
|
|
143
|
+
* Utility functions for HttpOutgoing.
|
|
141
144
|
*/
|
|
142
145
|
export declare namespace HttpOutgoing {
|
|
146
|
+
/**
|
|
147
|
+
* Creates an HttpOutgoing instance from various sources.
|
|
148
|
+
*
|
|
149
|
+
* @param instance - The source instance.
|
|
150
|
+
* @returns The HttpOutgoing instance.
|
|
151
|
+
*/
|
|
143
152
|
function from(instance: HttpOutgoing | NodeOutgoingMessage.Initiator): HttpOutgoing;
|
|
144
153
|
}
|
|
@@ -3,10 +3,16 @@ import { HttpOutgoingHost } from '../impl/http-outgoing.host.js';
|
|
|
3
3
|
import { isHttpOutgoing, isNodeOutgoingMessage } from '../type-guards.js';
|
|
4
4
|
import { NodeOutgoingMessage } from './node-outgoing-message.interface.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Utility functions for HttpOutgoing.
|
|
7
7
|
*/
|
|
8
8
|
export var HttpOutgoing;
|
|
9
9
|
(function (HttpOutgoing) {
|
|
10
|
+
/**
|
|
11
|
+
* Creates an HttpOutgoing instance from various sources.
|
|
12
|
+
*
|
|
13
|
+
* @param instance - The source instance.
|
|
14
|
+
* @returns The HttpOutgoing instance.
|
|
15
|
+
*/
|
|
10
16
|
function from(instance) {
|
|
11
17
|
if (isHttpOutgoing(instance))
|
|
12
18
|
return instance;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/http",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.4",
|
|
4
4
|
"description": "Opra Http Server Adapter",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"yaml": "^2.8.3"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@opra/common": "^1.26.
|
|
42
|
-
"@opra/core": "^1.26.
|
|
41
|
+
"@opra/common": "^1.26.4",
|
|
42
|
+
"@opra/core": "^1.26.4"
|
|
43
43
|
},
|
|
44
44
|
"optionalDependencies": {
|
|
45
45
|
"express": "^4.0.0 || ^5.0.0",
|
package/type-guards.d.ts
CHANGED
|
@@ -2,7 +2,31 @@ import type { HttpIncoming } from './interfaces/http-incoming.interface.js';
|
|
|
2
2
|
import type { HttpOutgoing } from './interfaces/http-outgoing.interface.js';
|
|
3
3
|
import type { NodeIncomingMessage } from './interfaces/node-incoming-message.interface.js';
|
|
4
4
|
import type { NodeOutgoingMessage } from './interfaces/node-outgoing-message.interface.js';
|
|
5
|
+
/**
|
|
6
|
+
* Checks if the given value is a NodeIncomingMessage.
|
|
7
|
+
*
|
|
8
|
+
* @param v - The value to check.
|
|
9
|
+
* @returns True if the value is a NodeIncomingMessage, false otherwise.
|
|
10
|
+
*/
|
|
5
11
|
export declare function isNodeIncomingMessage(v: any): v is NodeIncomingMessage;
|
|
12
|
+
/**
|
|
13
|
+
* Checks if the given value is an HttpIncoming instance.
|
|
14
|
+
*
|
|
15
|
+
* @param v - The value to check.
|
|
16
|
+
* @returns True if the value is an HttpIncoming instance, false otherwise.
|
|
17
|
+
*/
|
|
6
18
|
export declare function isHttpIncoming(v: any): v is HttpIncoming;
|
|
19
|
+
/**
|
|
20
|
+
* Checks if the given value is a NodeOutgoingMessage.
|
|
21
|
+
*
|
|
22
|
+
* @param v - The value to check.
|
|
23
|
+
* @returns True if the value is a NodeOutgoingMessage, false otherwise.
|
|
24
|
+
*/
|
|
7
25
|
export declare function isNodeOutgoingMessage(v: any): v is NodeOutgoingMessage;
|
|
26
|
+
/**
|
|
27
|
+
* Checks if the given value is an HttpOutgoing instance.
|
|
28
|
+
*
|
|
29
|
+
* @param v - The value to check.
|
|
30
|
+
* @returns True if the value is an HttpOutgoing instance, false otherwise.
|
|
31
|
+
*/
|
|
8
32
|
export declare function isHttpOutgoing(v: any): v is HttpOutgoing;
|
package/type-guards.js
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
import { isReadable, isStream } from '@opra/common';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if the given value is a NodeIncomingMessage.
|
|
4
|
+
*
|
|
5
|
+
* @param v - The value to check.
|
|
6
|
+
* @returns True if the value is a NodeIncomingMessage, false otherwise.
|
|
7
|
+
*/
|
|
2
8
|
export function isNodeIncomingMessage(v) {
|
|
3
9
|
return (v &&
|
|
4
10
|
typeof v.method === 'string' &&
|
|
5
11
|
Array.isArray(v.rawHeaders) &&
|
|
6
12
|
isReadable(v));
|
|
7
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the given value is an HttpIncoming instance.
|
|
16
|
+
*
|
|
17
|
+
* @param v - The value to check.
|
|
18
|
+
* @returns True if the value is an HttpIncoming instance, false otherwise.
|
|
19
|
+
*/
|
|
8
20
|
export function isHttpIncoming(v) {
|
|
9
21
|
return (isNodeIncomingMessage(v) &&
|
|
10
22
|
typeof v.header === 'function' &&
|
|
11
23
|
typeof v.acceptsLanguages === 'function' &&
|
|
12
24
|
typeof v.readBody === 'function');
|
|
13
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks if the given value is a NodeOutgoingMessage.
|
|
28
|
+
*
|
|
29
|
+
* @param v - The value to check.
|
|
30
|
+
* @returns True if the value is a NodeOutgoingMessage, false otherwise.
|
|
31
|
+
*/
|
|
14
32
|
export function isNodeOutgoingMessage(v) {
|
|
15
33
|
return v && typeof v.getHeaders === 'function' && isStream(v);
|
|
16
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks if the given value is an HttpOutgoing instance.
|
|
37
|
+
*
|
|
38
|
+
* @param v - The value to check.
|
|
39
|
+
* @returns True if the value is an HttpOutgoing instance, false otherwise.
|
|
40
|
+
*/
|
|
17
41
|
export function isHttpOutgoing(v) {
|
|
18
42
|
return (isNodeOutgoingMessage(v) &&
|
|
19
43
|
typeof v.clearCookie === 'function' &&
|
package/utils/body-reader.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export declare namespace BodyReader {
|
|
|
15
15
|
}
|
|
16
16
|
type Callback = (...args: any[]) => any;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* BodyReader is responsible for reading and parsing the request body.
|
|
19
|
+
* It supports various content encodings and can save the body to a file.
|
|
20
20
|
*/
|
|
21
21
|
export declare class BodyReader extends EventEmitter {
|
|
22
22
|
readonly req: HttpIncoming;
|
|
@@ -32,11 +32,25 @@ export declare class BodyReader extends EventEmitter {
|
|
|
32
32
|
protected onData: Callback;
|
|
33
33
|
protected onEnd: Callback;
|
|
34
34
|
constructor(req: HttpIncoming, options?: BodyReader.Options);
|
|
35
|
+
/**
|
|
36
|
+
* Reads the request body.
|
|
37
|
+
*
|
|
38
|
+
* @returns A promise that resolves to the body content (string, Buffer, or LocalFile).
|
|
39
|
+
* @throws {@link InternalServerError} If the stream is already read or not readable.
|
|
40
|
+
* @throws {@link OpraHttpError} If the content size exceeds the limit.
|
|
41
|
+
*/
|
|
35
42
|
read(): Promise<string | Buffer<ArrayBufferLike> | undefined>;
|
|
36
43
|
protected _onEnd(error: any): void;
|
|
37
44
|
protected _cleanup(): void;
|
|
38
45
|
protected _onAborted(): void;
|
|
39
46
|
protected _onData(chunk: Buffer | string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Static method to read the request body.
|
|
49
|
+
*
|
|
50
|
+
* @param req - The incoming HTTP request.
|
|
51
|
+
* @param options - Optional reader settings.
|
|
52
|
+
* @returns A promise that resolves to the body content.
|
|
53
|
+
*/
|
|
40
54
|
static read(req: HttpIncoming, options?: BodyReader.Options): Promise<string | Buffer | undefined>;
|
|
41
55
|
protected static encoderPipeline(req: HttpIncoming): nodeStream.Readable;
|
|
42
56
|
}
|
package/utils/body-reader.js
CHANGED
|
@@ -11,8 +11,8 @@ import { Writable } from 'stream';
|
|
|
11
11
|
import * as zlib from 'zlib';
|
|
12
12
|
import { LocalFile } from '../impl/local-file.js';
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* BodyReader is responsible for reading and parsing the request body.
|
|
15
|
+
* It supports various content encodings and can save the body to a file.
|
|
16
16
|
*/
|
|
17
17
|
export class BodyReader extends EventEmitter {
|
|
18
18
|
req;
|
|
@@ -53,6 +53,13 @@ export class BodyReader extends EventEmitter {
|
|
|
53
53
|
if (this._file)
|
|
54
54
|
this._file.autoDelete = true;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Reads the request body.
|
|
58
|
+
*
|
|
59
|
+
* @returns A promise that resolves to the body content (string, Buffer, or LocalFile).
|
|
60
|
+
* @throws {@link InternalServerError} If the stream is already read or not readable.
|
|
61
|
+
* @throws {@link OpraHttpError} If the content size exceeds the limit.
|
|
62
|
+
*/
|
|
56
63
|
async read() {
|
|
57
64
|
/* istanbul ignore next */
|
|
58
65
|
if (this._completed) {
|
|
@@ -209,6 +216,13 @@ export class BodyReader extends EventEmitter {
|
|
|
209
216
|
this._buffer.push(chunk);
|
|
210
217
|
}
|
|
211
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Static method to read the request body.
|
|
221
|
+
*
|
|
222
|
+
* @param req - The incoming HTTP request.
|
|
223
|
+
* @param options - Optional reader settings.
|
|
224
|
+
* @returns A promise that resolves to the body content.
|
|
225
|
+
*/
|
|
212
226
|
static async read(req, options) {
|
|
213
227
|
const bodyReady = new BodyReader(req, options);
|
|
214
228
|
return bodyReady.read();
|
package/utils/common.d.ts
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Verifies that the given val is a valid HTTP token
|
|
3
|
-
*
|
|
4
|
-
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
2
|
+
* Verifies that the given val is a valid HTTP token per the rules defined in RFC 7230.
|
|
3
|
+
* See {@link https://tools.ietf.org/html/rfc7230#section-3.2.6}
|
|
5
4
|
*
|
|
6
|
-
*
|
|
5
|
+
* @param val - The value to check.
|
|
6
|
+
* @returns True if the value is a valid HTTP token, false otherwise.
|
|
7
7
|
*/
|
|
8
8
|
export declare function checkIsHttpToken(val: any): boolean;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Hides internal Node.js stack frames for a given function.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* @param fn - The function to hide stack frames for.
|
|
13
|
+
* @returns The function with hidden stack frames.
|
|
13
14
|
*/
|
|
14
15
|
export declare function hideStackFrames(fn: Function): Function;
|
|
15
16
|
export declare const validateHeaderName: Function;
|
|
16
17
|
export declare const validateHeaderValue: Function;
|
|
18
|
+
/**
|
|
19
|
+
* Validates if the given value is a string.
|
|
20
|
+
*
|
|
21
|
+
* @param value - The value to validate.
|
|
22
|
+
* @param name - The name of the argument being validated.
|
|
23
|
+
* @throws {@link TypeError} If the value is not a string.
|
|
24
|
+
*/
|
|
17
25
|
export declare function validateString(value: any, name?: string): void;
|
package/utils/common.js
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
|
|
6
6
|
const nodeInternalPrefix = '__node_internal_';
|
|
7
7
|
/**
|
|
8
|
-
* Verifies that the given val is a valid HTTP token
|
|
9
|
-
*
|
|
10
|
-
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
8
|
+
* Verifies that the given val is a valid HTTP token per the rules defined in RFC 7230.
|
|
9
|
+
* See {@link https://tools.ietf.org/html/rfc7230#section-3.2.6}
|
|
11
10
|
*
|
|
12
|
-
*
|
|
11
|
+
* @param val - The value to check.
|
|
12
|
+
* @returns True if the value is a valid HTTP token, false otherwise.
|
|
13
13
|
*/
|
|
14
14
|
export function checkIsHttpToken(val) {
|
|
15
15
|
return typeof val === 'string' && tokenRegExp.exec(val) !== null;
|
|
@@ -28,9 +28,10 @@ function checkInvalidHeaderChar(val) {
|
|
|
28
28
|
return typeof val === 'string' && headerCharRegex.exec(val) !== null;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Hides internal Node.js stack frames for a given function.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
33
|
+
* @param fn - The function to hide stack frames for.
|
|
34
|
+
* @returns The function with hidden stack frames.
|
|
34
35
|
*/
|
|
35
36
|
export function hideStackFrames(fn) {
|
|
36
37
|
// We rename the functions that will be hidden to cut off the stacktrace
|
|
@@ -54,6 +55,13 @@ export const validateHeaderValue = hideStackFrames((name, value) => {
|
|
|
54
55
|
throw new TypeError(`Invalid character in header content ["${name}"]`);
|
|
55
56
|
}
|
|
56
57
|
});
|
|
58
|
+
/**
|
|
59
|
+
* Validates if the given value is a string.
|
|
60
|
+
*
|
|
61
|
+
* @param value - The value to validate.
|
|
62
|
+
* @param name - The name of the argument being validated.
|
|
63
|
+
* @throws {@link TypeError} If the value is not a string.
|
|
64
|
+
*/
|
|
57
65
|
export function validateString(value, name) {
|
|
58
66
|
if (typeof value !== 'string') {
|
|
59
67
|
throw new TypeError(`Invalid ${name ? name + ' ' : ''}argument. Value must be a string`);
|
package/utils/wrap-exception.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { BadRequestError, FailedDependencyError, ForbiddenError, InternalServerError, MethodNotAllowedError, NotAcceptableError, NotFoundError, OpraHttpError, UnauthorizedError, UnprocessableEntityError, } from '@opra/common';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps an error into an OpraHttpError.
|
|
4
|
+
*
|
|
5
|
+
* @param error - The error to wrap.
|
|
6
|
+
* @returns The wrapped OpraHttpError.
|
|
7
|
+
*/
|
|
2
8
|
export function wrapException(error) {
|
|
3
9
|
if (error instanceof OpraHttpError)
|
|
4
10
|
return error;
|