@opra/core 0.21.0 → 0.23.0
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/cjs/adapter/execution-context.host.js +48 -0
- package/cjs/adapter/http/express-adapter.host.js +24 -0
- package/cjs/adapter/http/express-adapter.js +12 -45
- package/cjs/adapter/http/helpers/concat-readable.js +20 -0
- package/cjs/adapter/http/helpers/multipart-helper.js +96 -0
- package/cjs/adapter/http/helpers/query-parsers.js +16 -0
- package/cjs/adapter/http/http-adapter-base.js +127 -0
- package/cjs/adapter/http/http-adapter.host.js +57 -0
- package/cjs/adapter/http/http-adapter.js +11 -129
- package/cjs/adapter/http/{impl/http-server-request.js → http-server-request.js} +11 -5
- package/cjs/adapter/http/{impl/http-server-response.js → http-server-response.js} +22 -22
- package/cjs/adapter/http/impl/http-incoming-message.host.js +148 -0
- package/cjs/adapter/http/impl/{http-outgoing-message-host.js → http-outgoing-message.host.js} +26 -38
- package/cjs/adapter/http/request-handlers/entity-request-handler.js +378 -0
- package/cjs/adapter/http/request-handlers/request-handler-base.js +27 -0
- package/cjs/adapter/http/request-handlers/storage-request-handler.js +134 -0
- package/cjs/adapter/operation-context.js +16 -0
- package/cjs/adapter/platform-adapter.host.js +107 -0
- package/cjs/adapter/request.host.js +1 -2
- package/cjs/adapter/request.js +2 -0
- package/cjs/adapter/response.js +2 -0
- package/cjs/adapter/services/logger.js +36 -0
- package/cjs/augmentation/collection.augmentation.js +2 -0
- package/cjs/augmentation/singleton.augmentation.js +2 -0
- package/cjs/augmentation/storage.augmentation.js +2 -0
- package/cjs/index.js +15 -9
- package/esm/adapter/execution-context.host.js +44 -0
- package/esm/adapter/http/express-adapter.host.js +20 -0
- package/esm/adapter/http/express-adapter.js +11 -20
- package/esm/adapter/http/helpers/concat-readable.js +16 -0
- package/esm/adapter/http/helpers/multipart-helper.js +91 -0
- package/esm/adapter/http/helpers/query-parsers.js +12 -0
- package/esm/adapter/http/http-adapter-base.js +123 -0
- package/esm/adapter/http/http-adapter.host.js +52 -0
- package/esm/adapter/http/http-adapter.js +11 -128
- package/esm/adapter/http/{impl/http-server-request.js → http-server-request.js} +12 -6
- package/esm/adapter/http/{impl/http-server-response.js → http-server-response.js} +22 -22
- package/esm/adapter/http/impl/http-incoming-message.host.js +144 -0
- package/esm/adapter/http/impl/{http-outgoing-message-host.js → http-outgoing-message.host.js} +25 -36
- package/esm/adapter/http/request-handlers/entity-request-handler.js +373 -0
- package/esm/adapter/http/request-handlers/request-handler-base.js +23 -0
- package/esm/adapter/http/request-handlers/storage-request-handler.js +129 -0
- package/esm/adapter/operation-context.js +13 -0
- package/esm/adapter/platform-adapter.host.js +102 -0
- package/esm/adapter/request.host.js +1 -2
- package/esm/adapter/request.js +1 -0
- package/esm/adapter/response.js +1 -0
- package/esm/adapter/services/logger.js +32 -0
- package/esm/augmentation/collection.augmentation.js +1 -0
- package/esm/augmentation/singleton.augmentation.js +1 -0
- package/esm/augmentation/storage.augmentation.js +1 -0
- package/esm/index.js +15 -9
- package/i18n/en/error.json +5 -2
- package/package.json +8 -7
- package/types/adapter/execution-context.d.ts +31 -0
- package/types/adapter/execution-context.host.d.ts +27 -0
- package/types/adapter/http/express-adapter.d.ts +12 -8
- package/types/adapter/http/express-adapter.host.d.ts +11 -0
- package/types/adapter/http/helpers/concat-readable.d.ts +3 -0
- package/types/adapter/http/helpers/multipart-helper.d.ts +25 -0
- package/types/adapter/http/helpers/query-parsers.d.ts +1 -0
- package/types/adapter/http/http-adapter-base.d.ts +23 -0
- package/types/adapter/http/http-adapter.d.ts +13 -29
- package/types/adapter/http/http-adapter.host.d.ts +18 -0
- package/types/adapter/http/{impl/http-server-request.d.ts → http-server-request.d.ts} +7 -6
- package/types/adapter/http/{impl/http-server-response.d.ts → http-server-response.d.ts} +2 -2
- package/types/adapter/http/impl/{http-incoming-message-host.d.ts → http-incoming-message.host.d.ts} +16 -12
- package/types/adapter/http/impl/{http-outgoing-message-host.d.ts → http-outgoing-message.host.d.ts} +12 -16
- package/types/adapter/http/request-handlers/entity-request-handler.d.ts +24 -0
- package/types/adapter/http/request-handlers/request-handler-base.d.ts +15 -0
- package/types/adapter/http/request-handlers/storage-request-handler.d.ts +23 -0
- package/types/adapter/interfaces/logger.interface.d.ts +7 -6
- package/types/adapter/interfaces/request-handler.interface.d.ts +4 -0
- package/types/adapter/operation-context.d.ts +11 -0
- package/types/adapter/{adapter.d.ts → platform-adapter.d.ts} +18 -28
- package/types/adapter/platform-adapter.host.d.ts +31 -0
- package/types/adapter/request.d.ts +11 -0
- package/types/adapter/request.host.d.ts +12 -21
- package/types/adapter/{interfaces/response.interface.d.ts → response.d.ts} +2 -2
- package/types/adapter/response.host.d.ts +2 -2
- package/types/adapter/services/logger.d.ts +14 -0
- package/types/augmentation/collection.augmentation.d.ts +112 -0
- package/types/augmentation/singleton.augmentation.d.ts +64 -0
- package/types/augmentation/storage.augmentation.d.ts +39 -0
- package/types/index.d.ts +15 -9
- package/cjs/adapter/adapter.js +0 -118
- package/cjs/adapter/http/impl/http-incoming-message-host.js +0 -127
- package/cjs/adapter/http/request-parsers/parse-collection-request.js +0 -165
- package/cjs/adapter/http/request-parsers/parse-request.js +0 -24
- package/cjs/adapter/http/request-parsers/parse-singleton-request.js +0 -96
- package/cjs/adapter/internal/metadata.resource.js +0 -26
- package/cjs/adapter/request-context.host.js +0 -44
- package/cjs/shared/collection-resource-base.js +0 -20
- package/esm/adapter/adapter.js +0 -113
- package/esm/adapter/http/impl/http-incoming-message-host.js +0 -122
- package/esm/adapter/http/request-parsers/parse-collection-request.js +0 -161
- package/esm/adapter/http/request-parsers/parse-request.js +0 -20
- package/esm/adapter/http/request-parsers/parse-singleton-request.js +0 -92
- package/esm/adapter/internal/metadata.resource.js +0 -23
- package/esm/adapter/request-context.host.js +0 -40
- package/esm/shared/collection-resource-base.js +0 -16
- package/types/adapter/http/request-parsers/parse-collection-request.d.ts +0 -4
- package/types/adapter/http/request-parsers/parse-request.d.ts +0 -4
- package/types/adapter/http/request-parsers/parse-singleton-request.d.ts +0 -4
- package/types/adapter/interfaces/request-context.interface.d.ts +0 -31
- package/types/adapter/interfaces/request.interface.d.ts +0 -15
- package/types/adapter/internal/metadata.resource.d.ts +0 -7
- package/types/adapter/request-context.host.d.ts +0 -22
- package/types/shared/collection-resource-base.d.ts +0 -11
- /package/cjs/adapter/{interfaces/request-context.interface.js → execution-context.js} +0 -0
- /package/cjs/adapter/http/{request-parsers/batch-request-parser.js → request-handlers/parse-batch-request.js} +0 -0
- /package/cjs/adapter/interfaces/{request.interface.js → request-handler.interface.js} +0 -0
- /package/cjs/adapter/{interfaces/response.interface.js → platform-adapter.js} +0 -0
- /package/esm/adapter/{interfaces/request-context.interface.js → execution-context.js} +0 -0
- /package/esm/adapter/http/{request-parsers/batch-request-parser.js → request-handlers/parse-batch-request.js} +0 -0
- /package/esm/adapter/interfaces/{request.interface.js → request-handler.interface.js} +0 -0
- /package/esm/adapter/{interfaces/response.interface.js → platform-adapter.js} +0 -0
- /package/types/adapter/http/{request-parsers/batch-request-parser.d.ts → request-handlers/parse-batch-request.d.ts} +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Logger = void 0;
|
|
4
|
+
class Logger {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this._instance = options.instance ||
|
|
7
|
+
(!(process.env.NODE_ENV || '').includes('test') ? globalThis.console : {});
|
|
8
|
+
}
|
|
9
|
+
info(message, ...optionalParams) {
|
|
10
|
+
(this._instance.info || this._instance.log)?.(message, ...optionalParams);
|
|
11
|
+
}
|
|
12
|
+
error(message, ...optionalParams) {
|
|
13
|
+
if (this._instance.error) {
|
|
14
|
+
this._instance.error(message, ...optionalParams);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
this.info(message, ...optionalParams);
|
|
18
|
+
}
|
|
19
|
+
fatal(message, ...optionalParams) {
|
|
20
|
+
if (this._instance.fatal) {
|
|
21
|
+
this._instance.fatal(message, ...optionalParams);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
this.error(message, ...optionalParams);
|
|
25
|
+
}
|
|
26
|
+
warn(message, ...optionalParams) {
|
|
27
|
+
this._instance.warn?.(message, ...optionalParams);
|
|
28
|
+
}
|
|
29
|
+
debug(message, ...optionalParams) {
|
|
30
|
+
this._instance.debug?.(message, ...optionalParams);
|
|
31
|
+
}
|
|
32
|
+
verbose(message, ...optionalParams) {
|
|
33
|
+
this._instance.verbose?.(message, ...optionalParams);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.Logger = Logger;
|
package/cjs/index.js
CHANGED
|
@@ -3,16 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
require("reflect-metadata");
|
|
5
5
|
require("./augmentation/resource.augmentation.js");
|
|
6
|
+
require("./augmentation/collection.augmentation.js");
|
|
7
|
+
require("./augmentation/singleton.augmentation.js");
|
|
8
|
+
require("./augmentation/storage.augmentation.js");
|
|
6
9
|
tslib_1.__exportStar(require("./types.js"), exports);
|
|
7
|
-
tslib_1.__exportStar(require("./adapter/
|
|
10
|
+
tslib_1.__exportStar(require("./adapter/execution-context.js"), exports);
|
|
11
|
+
tslib_1.__exportStar(require("./adapter/operation-context.js"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./adapter/platform-adapter.js"), exports);
|
|
13
|
+
tslib_1.__exportStar(require("./adapter/request.js"), exports);
|
|
14
|
+
tslib_1.__exportStar(require("./adapter/response.js"), exports);
|
|
8
15
|
tslib_1.__exportStar(require("./adapter/http/express-adapter.js"), exports);
|
|
9
16
|
tslib_1.__exportStar(require("./adapter/http/http-adapter.js"), exports);
|
|
10
|
-
tslib_1.__exportStar(require("./adapter/http/impl/http-
|
|
11
|
-
tslib_1.__exportStar(require("./adapter/http/impl/http-
|
|
12
|
-
tslib_1.__exportStar(require("./adapter/http/
|
|
13
|
-
tslib_1.__exportStar(require("./adapter/http/
|
|
14
|
-
tslib_1.__exportStar(require("./adapter/interfaces/request-context.interface.js"), exports);
|
|
17
|
+
tslib_1.__exportStar(require("./adapter/http/impl/http-incoming-message.host.js"), exports);
|
|
18
|
+
tslib_1.__exportStar(require("./adapter/http/impl/http-outgoing-message.host.js"), exports);
|
|
19
|
+
tslib_1.__exportStar(require("./adapter/http/http-server-request.js"), exports);
|
|
20
|
+
tslib_1.__exportStar(require("./adapter/http/http-server-response.js"), exports);
|
|
15
21
|
tslib_1.__exportStar(require("./adapter/interfaces/logger.interface.js"), exports);
|
|
16
|
-
tslib_1.__exportStar(require("./adapter/interfaces/request.interface.js"), exports);
|
|
17
|
-
tslib_1.__exportStar(require("./adapter/
|
|
18
|
-
tslib_1.__exportStar(require("./
|
|
22
|
+
tslib_1.__exportStar(require("./adapter/interfaces/request-handler.interface.js"), exports);
|
|
23
|
+
tslib_1.__exportStar(require("./adapter/services/logger.js"), exports);
|
|
24
|
+
tslib_1.__exportStar(require("./adapter/http/helpers/multipart-helper.js"), exports);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
2
|
+
export class ExecutionContextHost extends AsyncEventEmitter {
|
|
3
|
+
constructor(api, platform, protocol) {
|
|
4
|
+
super();
|
|
5
|
+
this.api = api;
|
|
6
|
+
this.platform = platform;
|
|
7
|
+
this.errors = [];
|
|
8
|
+
this.executionScope = {};
|
|
9
|
+
this.ws = protocol.ws;
|
|
10
|
+
this.rpc = protocol.rpc;
|
|
11
|
+
if (protocol.http) {
|
|
12
|
+
this.protocol = 'http';
|
|
13
|
+
this.http = {
|
|
14
|
+
platform,
|
|
15
|
+
incoming: protocol.http.incoming,
|
|
16
|
+
outgoing: protocol.http.outgoing,
|
|
17
|
+
switchToContext: () => this
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
else if (protocol.ws) {
|
|
21
|
+
this.protocol = 'ws';
|
|
22
|
+
this.ws = protocol.ws;
|
|
23
|
+
}
|
|
24
|
+
else if (protocol.rpc) {
|
|
25
|
+
this.protocol = 'rpc';
|
|
26
|
+
this.rpc = protocol.rpc;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
switchToHttp() {
|
|
30
|
+
if (this.http)
|
|
31
|
+
return this.http;
|
|
32
|
+
throw new TypeError('Not executing in an "Http" context');
|
|
33
|
+
}
|
|
34
|
+
switchToWs() {
|
|
35
|
+
if (this.ws)
|
|
36
|
+
return this.ws;
|
|
37
|
+
throw new TypeError('Not executing in an "WebSocket" context');
|
|
38
|
+
}
|
|
39
|
+
switchToRpc() {
|
|
40
|
+
if (this.rpc)
|
|
41
|
+
return this.rpc;
|
|
42
|
+
throw new TypeError('Not executing in an "RPC" context');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OpraURLPath } from '@opra/common';
|
|
2
|
+
import { HttpAdapterBase } from './http-adapter-base.js';
|
|
3
|
+
import { HttpServerRequest } from './http-server-request.js';
|
|
4
|
+
import { HttpServerResponse } from './http-server-response.js';
|
|
5
|
+
export class ExpressAdapterHost extends HttpAdapterBase {
|
|
6
|
+
constructor(app, api, options) {
|
|
7
|
+
super(api, options);
|
|
8
|
+
this._platform = 'express';
|
|
9
|
+
const basePath = new OpraURLPath(options?.basePath);
|
|
10
|
+
app.use(basePath.toString(), (_req, _res) => {
|
|
11
|
+
const req = HttpServerRequest.from(_req);
|
|
12
|
+
const res = HttpServerResponse.from(_res);
|
|
13
|
+
this.handleIncoming(req, res)
|
|
14
|
+
.catch(() => void 0);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
get app() {
|
|
18
|
+
return this._app;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
static async create(app, document, options) {
|
|
11
|
-
const express = await import('express');
|
|
12
|
-
const adapter = new OpraExpressAdapter(document);
|
|
13
|
-
await adapter.init(options);
|
|
14
|
-
const prefix = '/' + normalizePath(options?.prefix, true);
|
|
15
|
-
app.use(prefix, express.json());
|
|
16
|
-
app.use(prefix, (req, res, next) => {
|
|
17
|
-
adapter.handler(HttpServerRequest.create(req), HttpServerResponse.create(res))
|
|
18
|
-
.catch(e => next(e));
|
|
19
|
-
});
|
|
1
|
+
import { ExpressAdapterHost } from './express-adapter.host.js';
|
|
2
|
+
/**
|
|
3
|
+
* @namespace
|
|
4
|
+
*/
|
|
5
|
+
export var ExpressAdapter;
|
|
6
|
+
(function (ExpressAdapter) {
|
|
7
|
+
async function create(app, api, options) {
|
|
8
|
+
const adapter = new ExpressAdapterHost(app, api, options);
|
|
9
|
+
await adapter.init();
|
|
20
10
|
return adapter;
|
|
21
11
|
}
|
|
22
|
-
|
|
12
|
+
ExpressAdapter.create = create;
|
|
13
|
+
})(ExpressAdapter || (ExpressAdapter = {}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PassThrough } from 'stream';
|
|
2
|
+
export function concatReadable(...streams) {
|
|
3
|
+
const out = new PassThrough();
|
|
4
|
+
const pipeNext = () => {
|
|
5
|
+
const nextStream = streams.shift();
|
|
6
|
+
if (nextStream) {
|
|
7
|
+
nextStream.pipe(out, { end: false });
|
|
8
|
+
nextStream.once('end', () => pipeNext());
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
out.end();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
pipeNext();
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import formidable from 'formidable';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
const noOp = () => void 0;
|
|
5
|
+
export class MultipartIterator extends EventEmitter {
|
|
6
|
+
constructor(incoming, options) {
|
|
7
|
+
super();
|
|
8
|
+
this._cancelled = false;
|
|
9
|
+
this._items = [];
|
|
10
|
+
this._stack = [];
|
|
11
|
+
this.setMaxListeners(1000);
|
|
12
|
+
const form = this._form = formidable({
|
|
13
|
+
...options,
|
|
14
|
+
filter: (part) => {
|
|
15
|
+
return !this._cancelled && (!options?.filter || options.filter(part));
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
form.once('error', () => {
|
|
19
|
+
this._cancelled = true;
|
|
20
|
+
if (this.listenerCount('error') > 0)
|
|
21
|
+
this.emit('error');
|
|
22
|
+
});
|
|
23
|
+
form.on('field', (field, value) => {
|
|
24
|
+
const item = { field, value };
|
|
25
|
+
this._items.push(item);
|
|
26
|
+
this._stack.push(item);
|
|
27
|
+
this.emit('item', item);
|
|
28
|
+
});
|
|
29
|
+
form.on('file', (field, file) => {
|
|
30
|
+
const item = { field, file };
|
|
31
|
+
this._items.push(item);
|
|
32
|
+
this._stack.push(item);
|
|
33
|
+
this.emit('item', item);
|
|
34
|
+
});
|
|
35
|
+
form.parse(incoming).catch(noOp);
|
|
36
|
+
}
|
|
37
|
+
get items() {
|
|
38
|
+
return this._items;
|
|
39
|
+
}
|
|
40
|
+
getNext() {
|
|
41
|
+
if (this._form.ended)
|
|
42
|
+
return Promise.resolve(undefined);
|
|
43
|
+
this.resume();
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
if (this._stack.length)
|
|
46
|
+
return resolve(this._stack.shift());
|
|
47
|
+
this.once('item', () => resolve(this._stack.shift()));
|
|
48
|
+
this.once('error', (e) => reject(e));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
getAll() {
|
|
52
|
+
if (this._form.ended)
|
|
53
|
+
return Promise.resolve([...this._items]);
|
|
54
|
+
this.resume();
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
this._form.once('error', reject);
|
|
57
|
+
this._form.once('end', () => {
|
|
58
|
+
resolve([...this._items]);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
cancel() {
|
|
63
|
+
this._cancelled = true;
|
|
64
|
+
this.resume();
|
|
65
|
+
}
|
|
66
|
+
resume() {
|
|
67
|
+
this._form.resume();
|
|
68
|
+
}
|
|
69
|
+
pause() {
|
|
70
|
+
this._form.pause();
|
|
71
|
+
}
|
|
72
|
+
async deleteFiles() {
|
|
73
|
+
const promises = [];
|
|
74
|
+
this._items
|
|
75
|
+
.forEach(item => {
|
|
76
|
+
if (!item.file)
|
|
77
|
+
return;
|
|
78
|
+
const file = item.file;
|
|
79
|
+
promises.push(new Promise(resolve => {
|
|
80
|
+
if (file._writeStream.closed)
|
|
81
|
+
return resolve();
|
|
82
|
+
file._writeStream.once('close', resolve);
|
|
83
|
+
}).then(() => {
|
|
84
|
+
return fs.unlink(file.filepath);
|
|
85
|
+
}).then(() => {
|
|
86
|
+
return 0;
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
return Promise.allSettled(promises);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { BadRequestError, HttpHeaderCodes, HttpStatusCodes, InternalServerError, IssueSeverity, OpraException, OpraSchema, wrapException } from '@opra/common';
|
|
2
|
+
import { ExecutionContextHost } from '../execution-context.host.js';
|
|
3
|
+
import { PlatformAdapterHost } from '../platform-adapter.host.js';
|
|
4
|
+
import { EntityRequestHandler } from './request-handlers/entity-request-handler.js';
|
|
5
|
+
import { StorageRequestHandler } from './request-handlers/storage-request-handler.js';
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @class HttpAdapterBase
|
|
9
|
+
*/
|
|
10
|
+
export class HttpAdapterBase extends PlatformAdapterHost {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this._protocol = 'http';
|
|
14
|
+
this._requestHandlers = [
|
|
15
|
+
new EntityRequestHandler(this),
|
|
16
|
+
new StorageRequestHandler(this)
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Main http request handler
|
|
21
|
+
* @param incoming
|
|
22
|
+
* @param outgoing
|
|
23
|
+
* @protected
|
|
24
|
+
*/
|
|
25
|
+
async handleIncoming(incoming, outgoing) {
|
|
26
|
+
const context = new ExecutionContextHost(this.api, this.platform, { http: { incoming, outgoing } });
|
|
27
|
+
try {
|
|
28
|
+
/* istanbul ignore next */
|
|
29
|
+
if (!this._initialized)
|
|
30
|
+
throw new InternalServerError(`${Object.getPrototypeOf(this).constructor.name} has not been initialized yet`);
|
|
31
|
+
outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
32
|
+
// Expose headers if cors enabled
|
|
33
|
+
if (outgoing.getHeader(HttpHeaderCodes.Access_Control_Allow_Origin)) {
|
|
34
|
+
// Expose X-Opra-* headers
|
|
35
|
+
outgoing.appendHeader(HttpHeaderCodes.Access_Control_Expose_Headers, Object.values(HttpHeaderCodes)
|
|
36
|
+
.filter(k => k.toLowerCase().startsWith('x-opra-')));
|
|
37
|
+
}
|
|
38
|
+
await this.processRequest(context);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
context.errors.push(wrapException(error));
|
|
42
|
+
}
|
|
43
|
+
// If no response returned to the client we send an error
|
|
44
|
+
if (!outgoing.writableEnded) {
|
|
45
|
+
if (!context.errors.length)
|
|
46
|
+
context.errors.push(new BadRequestError(`Server can not process this request`));
|
|
47
|
+
await this.handleError(context);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async processRequest(context) {
|
|
51
|
+
try {
|
|
52
|
+
const { incoming, outgoing } = context.switchToHttp();
|
|
53
|
+
if (incoming.method === 'GET' &&
|
|
54
|
+
incoming.parsedUrl.path.length &&
|
|
55
|
+
incoming.parsedUrl.path[0].resource === '$metadata') {
|
|
56
|
+
outgoing.setHeader('content-type', 'application/json');
|
|
57
|
+
outgoing.end(JSON.stringify(this.api.exportSchema({ webSafe: true })));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const { parsedUrl } = incoming;
|
|
61
|
+
if (!parsedUrl.path.length) {
|
|
62
|
+
// Batch
|
|
63
|
+
if (incoming.headers['content-type'] === 'multipart/mixed') {
|
|
64
|
+
// todo
|
|
65
|
+
}
|
|
66
|
+
throw new BadRequestError();
|
|
67
|
+
}
|
|
68
|
+
// Iterate through request handlers until one of them sends response (end outgoing stream)
|
|
69
|
+
for (const requestHandler of this._requestHandlers) {
|
|
70
|
+
await requestHandler.processRequest(context);
|
|
71
|
+
if (outgoing.writableEnded)
|
|
72
|
+
return;
|
|
73
|
+
if (context.errors.length) {
|
|
74
|
+
await this.handleError(context);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await context.emitAsync('finish');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async handleError(context) {
|
|
84
|
+
const { errors } = context;
|
|
85
|
+
const { outgoing } = context.switchToHttp();
|
|
86
|
+
if (outgoing.headersSent) {
|
|
87
|
+
outgoing.end();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
errors.forEach(x => {
|
|
91
|
+
// todo. implement a better log mechanism
|
|
92
|
+
if (x instanceof OpraException)
|
|
93
|
+
this._logger.fatal(x);
|
|
94
|
+
else
|
|
95
|
+
this._logger.error(x);
|
|
96
|
+
});
|
|
97
|
+
const wrappedErrors = errors.map(wrapException);
|
|
98
|
+
// Sort errors from fatal to info
|
|
99
|
+
wrappedErrors.sort((a, b) => {
|
|
100
|
+
const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
|
|
101
|
+
if (i === 0)
|
|
102
|
+
return b.status - a.status;
|
|
103
|
+
return i;
|
|
104
|
+
});
|
|
105
|
+
let status = outgoing.statusCode || 0;
|
|
106
|
+
if (!status || status < Number(HttpStatusCodes.BAD_REQUEST)) {
|
|
107
|
+
status = wrappedErrors[0].status;
|
|
108
|
+
if (status < Number(HttpStatusCodes.BAD_REQUEST))
|
|
109
|
+
status = HttpStatusCodes.INTERNAL_SERVER_ERROR;
|
|
110
|
+
}
|
|
111
|
+
outgoing.statusCode = status;
|
|
112
|
+
const body = this._i18n.deep({
|
|
113
|
+
errors: wrappedErrors.map(x => x.issue)
|
|
114
|
+
});
|
|
115
|
+
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
116
|
+
outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
117
|
+
outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
|
|
118
|
+
outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
|
|
119
|
+
outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
120
|
+
outgoing.send(JSON.stringify(body));
|
|
121
|
+
outgoing.end();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import { HttpStatusCodes, HttpStatusMessages, OpraURL, OpraURLPath, } from '@opra/common';
|
|
3
|
+
import { HttpAdapterBase } from './http-adapter-base.js';
|
|
4
|
+
import { HttpServerRequest } from './http-server-request.js';
|
|
5
|
+
import { HttpServerResponse } from './http-server-response.js';
|
|
6
|
+
/**
|
|
7
|
+
* @class HttpAdapterHost
|
|
8
|
+
*/
|
|
9
|
+
export class HttpAdapterHost extends HttpAdapterBase {
|
|
10
|
+
constructor(api, options) {
|
|
11
|
+
super(api, options);
|
|
12
|
+
this._platform = 'http';
|
|
13
|
+
this._basePath = new OpraURLPath(options?.basePath);
|
|
14
|
+
this._server = http.createServer((incomingMessage, serverResponse) => this._serverListener(incomingMessage, serverResponse));
|
|
15
|
+
}
|
|
16
|
+
get basePath() {
|
|
17
|
+
return this._basePath;
|
|
18
|
+
}
|
|
19
|
+
get server() {
|
|
20
|
+
return this._server;
|
|
21
|
+
}
|
|
22
|
+
_serverListener(incomingMessage, serverResponse) {
|
|
23
|
+
const originalUrl = incomingMessage.url;
|
|
24
|
+
const parsedUrl = new OpraURL(originalUrl);
|
|
25
|
+
const relativePath = OpraURLPath.relative(parsedUrl.path, this.basePath);
|
|
26
|
+
if (!relativePath) {
|
|
27
|
+
serverResponse.statusCode = HttpStatusCodes.NOT_FOUND;
|
|
28
|
+
serverResponse.statusMessage = HttpStatusMessages[HttpStatusCodes.NOT_FOUND];
|
|
29
|
+
serverResponse.end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
parsedUrl.path = relativePath;
|
|
33
|
+
incomingMessage.originalUrl = originalUrl;
|
|
34
|
+
incomingMessage.baseUrl = this.basePath.toString();
|
|
35
|
+
incomingMessage.parsedUrl = parsedUrl;
|
|
36
|
+
const req = HttpServerRequest.from(incomingMessage);
|
|
37
|
+
const res = HttpServerResponse.from(serverResponse);
|
|
38
|
+
this.handleIncoming(req, res)
|
|
39
|
+
.catch((e) => this._logger.fatal(e));
|
|
40
|
+
}
|
|
41
|
+
async close() {
|
|
42
|
+
await super.close();
|
|
43
|
+
if (this.server.listening)
|
|
44
|
+
await new Promise((resolve, reject) => {
|
|
45
|
+
this.server.close((err) => {
|
|
46
|
+
if (err)
|
|
47
|
+
return reject(err);
|
|
48
|
+
resolve();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,130 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, OpraException, OpraSchema, Singleton, wrapException } from '@opra/common';
|
|
3
|
-
import { OpraAdapter } from '../adapter.js';
|
|
4
|
-
import { RequestContextHost } from '../request-context.host.js';
|
|
5
|
-
import { ResponseHost } from '../response.host.js';
|
|
6
|
-
import { parseRequest } from './request-parsers/parse-request.js';
|
|
1
|
+
import { HttpAdapterHost } from './http-adapter.host.js';
|
|
7
2
|
/**
|
|
8
|
-
*
|
|
9
|
-
* @class OpraHttpAdapter
|
|
3
|
+
* @namespace HttpAdapter
|
|
10
4
|
*/
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const request = await parseRequest(this._apiRoot, incoming);
|
|
22
|
-
const task = this.createRequestTask(request, outgoing);
|
|
23
|
-
await task.toPromise();
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
if (e instanceof OpraException)
|
|
27
|
-
throw e;
|
|
28
|
-
throw new BadRequestError(e);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
await this.handleError(incoming, outgoing, [error]);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
createRequestTask(request, outgoing) {
|
|
36
|
-
return new Task(async () => {
|
|
37
|
-
const response = new ResponseHost({ http: outgoing });
|
|
38
|
-
const context = new RequestContextHost('http', this.platform, this.api, request, response);
|
|
39
|
-
return this.executeRequest(context);
|
|
40
|
-
}, {
|
|
41
|
-
id: request.contentId,
|
|
42
|
-
exclusive: request.crud !== 'read'
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
async executeRequest(context) {
|
|
46
|
-
const { request, response } = context;
|
|
47
|
-
await super.executeRequest(context)
|
|
48
|
-
.catch(e => {
|
|
49
|
-
response.errors = response.errors || [];
|
|
50
|
-
response.errors.push(e);
|
|
51
|
-
});
|
|
52
|
-
const outgoing = response.switchToHttp();
|
|
53
|
-
if (response.errors?.length) {
|
|
54
|
-
const errors = response.errors.map(e => wrapException(e));
|
|
55
|
-
await this.handleError(request.switchToHttp(), outgoing, errors);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (request.resource instanceof Singleton || request.resource instanceof Collection) {
|
|
59
|
-
outgoing.setHeader(HttpHeaderCodes.X_Opra_Data_Type, String(request.resource.type.name));
|
|
60
|
-
outgoing.setHeader(HttpHeaderCodes.X_Opra_Operation, request.operation);
|
|
61
|
-
}
|
|
62
|
-
if (request.crud === 'create') {
|
|
63
|
-
if (!response.value)
|
|
64
|
-
throw new InternalServerError();
|
|
65
|
-
// todo validate
|
|
66
|
-
outgoing.statusCode = 201;
|
|
67
|
-
}
|
|
68
|
-
if (request.resource instanceof Collection &&
|
|
69
|
-
request.crud === 'read' && request.many && request.args.count >= 0) {
|
|
70
|
-
outgoing.setHeader(HttpHeaderCodes.X_Opra_Total_Matches, String(response.count));
|
|
71
|
-
}
|
|
72
|
-
outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
|
|
73
|
-
outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
74
|
-
outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
|
|
75
|
-
outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
|
|
76
|
-
outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
77
|
-
if (response.value) {
|
|
78
|
-
if (typeof response.value === 'object') {
|
|
79
|
-
if (isReadable(response.value) || Buffer.isBuffer(response.value))
|
|
80
|
-
outgoing.send(response.value);
|
|
81
|
-
else {
|
|
82
|
-
const body = this.i18n.deep(response.value);
|
|
83
|
-
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
84
|
-
outgoing.send(JSON.stringify(body));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
else
|
|
88
|
-
outgoing.send(JSON.stringify(response.value));
|
|
89
|
-
}
|
|
90
|
-
outgoing.end();
|
|
91
|
-
}
|
|
92
|
-
async handleError(incoming, outgoing, errors) {
|
|
93
|
-
errors.forEach(e => {
|
|
94
|
-
this.log((e instanceof OpraException) ? 'error' : 'fatal', incoming, e); // todo. implement a better logger
|
|
95
|
-
});
|
|
96
|
-
errors = errors.map(wrapException);
|
|
97
|
-
let status = outgoing.statusCode || 0;
|
|
98
|
-
// Sort errors from fatal to info
|
|
99
|
-
errors.sort((a, b) => {
|
|
100
|
-
const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
|
|
101
|
-
if (i === 0)
|
|
102
|
-
return b.status - a.status;
|
|
103
|
-
return i;
|
|
104
|
-
});
|
|
105
|
-
if (!status || status < HttpStatusCodes.BAD_REQUEST) {
|
|
106
|
-
status = errors[0].status;
|
|
107
|
-
if (status < HttpStatusCodes.BAD_REQUEST)
|
|
108
|
-
status = HttpStatusCodes.INTERNAL_SERVER_ERROR;
|
|
109
|
-
}
|
|
110
|
-
const body = this.i18n.deep({
|
|
111
|
-
errors: errors.map(e => e.issue)
|
|
112
|
-
});
|
|
113
|
-
outgoing.statusCode = status;
|
|
114
|
-
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
115
|
-
outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
116
|
-
outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
|
|
117
|
-
outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
|
|
118
|
-
outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
119
|
-
outgoing.send(JSON.stringify(body));
|
|
120
|
-
outgoing.end();
|
|
121
|
-
}
|
|
122
|
-
log(logType, incoming, message, ...optionalParams) {
|
|
123
|
-
const logFn = logType === 'fatal'
|
|
124
|
-
? this.logger?.fatal || this.logger?.error
|
|
125
|
-
: this.logger?.[logType];
|
|
126
|
-
if (!logFn)
|
|
127
|
-
return;
|
|
128
|
-
logFn.apply(this.logger, [String(message), ...optionalParams]);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
5
|
+
export var HttpAdapter;
|
|
6
|
+
(function (HttpAdapter) {
|
|
7
|
+
async function create(api, options) {
|
|
8
|
+
const adapter = new HttpAdapterHost(api, options);
|
|
9
|
+
await adapter.init();
|
|
10
|
+
return adapter;
|
|
11
|
+
}
|
|
12
|
+
HttpAdapter.create = create;
|
|
13
|
+
})(HttpAdapter || (HttpAdapter = {}));
|
|
@@ -6,22 +6,28 @@ import accepts from 'accepts';
|
|
|
6
6
|
import fresh from 'fresh';
|
|
7
7
|
import parseRange from 'range-parser';
|
|
8
8
|
import typeIs from 'type-is';
|
|
9
|
-
import { isReadable, mergePrototype } from '@opra/common';
|
|
10
|
-
import { HttpIncomingMessageHost } from './http-incoming-message
|
|
9
|
+
import { isReadable, mergePrototype, OpraURL } from '@opra/common';
|
|
10
|
+
import { HttpIncomingMessageHost } from './impl/http-incoming-message.host.js';
|
|
11
11
|
function isHttpIncomingMessage(v) {
|
|
12
12
|
return v && Array.isArray(v.rawHeaders) && isReadable(v);
|
|
13
13
|
}
|
|
14
14
|
export var HttpServerRequest;
|
|
15
15
|
(function (HttpServerRequest) {
|
|
16
|
-
function
|
|
16
|
+
function from(instance) {
|
|
17
17
|
if (!isHttpIncomingMessage(instance))
|
|
18
|
-
instance = HttpIncomingMessageHost.
|
|
18
|
+
instance = HttpIncomingMessageHost.from(instance);
|
|
19
19
|
mergePrototype(instance, HttpServerRequestHost.prototype);
|
|
20
|
-
|
|
20
|
+
const req = instance;
|
|
21
|
+
req.baseUrl = req.baseUrl || '';
|
|
22
|
+
req.parsedUrl = req.parsedUrl || new OpraURL(req.url);
|
|
23
|
+
return req;
|
|
21
24
|
}
|
|
22
|
-
HttpServerRequest.
|
|
25
|
+
HttpServerRequest.from = from;
|
|
23
26
|
})(HttpServerRequest || (HttpServerRequest = {}));
|
|
24
27
|
class HttpServerRequestHost {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.basePath = '/';
|
|
30
|
+
}
|
|
25
31
|
get protocol() {
|
|
26
32
|
const proto = this.header('X-Forwarded-Proto') || 'http';
|
|
27
33
|
const index = proto.indexOf(',');
|