@opra/core 0.22.0 → 0.23.1
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 -133
- 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} +4 -4
- 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 +405 -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 -132
- 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} +4 -4
- 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 +400 -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 +7 -2
- package/package.json +7 -6
- 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,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.severity) - IssueSeverity.Keys.indexOf(b.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 = {
|
|
113
|
+
errors: wrappedErrors.map(x => this._i18n.deep(x.toJSON()))
|
|
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,134 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, OpraException, OpraSchema, 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.crud === 'create') {
|
|
59
|
-
if (!response.value)
|
|
60
|
-
throw new InternalServerError();
|
|
61
|
-
// todo validate
|
|
62
|
-
outgoing.statusCode = 201;
|
|
63
|
-
}
|
|
64
|
-
if (request.resource instanceof Collection &&
|
|
65
|
-
request.crud === 'read' && request.many && request.args.count >= 0) {
|
|
66
|
-
outgoing.setHeader(HttpHeaderCodes.X_Total_Count, String(response.count));
|
|
67
|
-
}
|
|
68
|
-
outgoing.statusCode = outgoing.statusCode || HttpStatusCodes.OK;
|
|
69
|
-
outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
70
|
-
outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
|
|
71
|
-
outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
|
|
72
|
-
outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
73
|
-
// Expose headers if cors enabled
|
|
74
|
-
if (outgoing.getHeader(HttpHeaderCodes.Access_Control_Allow_Origin)) {
|
|
75
|
-
// Expose X-Total-Count header
|
|
76
|
-
outgoing.appendHeader(HttpHeaderCodes.Access_Control_Expose_Headers, [HttpHeaderCodes.X_Total_Count]);
|
|
77
|
-
// Expose X-Opra-* headers
|
|
78
|
-
outgoing.appendHeader(HttpHeaderCodes.Access_Control_Expose_Headers, Object.values(HttpHeaderCodes)
|
|
79
|
-
.filter(k => k.toLowerCase().startsWith('x-opra-')));
|
|
80
|
-
}
|
|
81
|
-
if (response.value) {
|
|
82
|
-
if (typeof response.value === 'object') {
|
|
83
|
-
if (isReadable(response.value) || Buffer.isBuffer(response.value))
|
|
84
|
-
outgoing.send(response.value);
|
|
85
|
-
else {
|
|
86
|
-
const body = this.i18n.deep(response.value);
|
|
87
|
-
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
88
|
-
outgoing.send(JSON.stringify(body));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
else
|
|
92
|
-
outgoing.send(JSON.stringify(response.value));
|
|
93
|
-
}
|
|
94
|
-
outgoing.end();
|
|
95
|
-
}
|
|
96
|
-
async handleError(incoming, outgoing, errors) {
|
|
97
|
-
errors.forEach(e => {
|
|
98
|
-
this.log((e instanceof OpraException) ? 'error' : 'fatal', incoming, e); // todo. implement a better logger
|
|
99
|
-
});
|
|
100
|
-
errors = errors.map(wrapException);
|
|
101
|
-
let status = outgoing.statusCode || 0;
|
|
102
|
-
// Sort errors from fatal to info
|
|
103
|
-
errors.sort((a, b) => {
|
|
104
|
-
const i = IssueSeverity.Keys.indexOf(a.issue.severity) - IssueSeverity.Keys.indexOf(b.issue.severity);
|
|
105
|
-
if (i === 0)
|
|
106
|
-
return b.status - a.status;
|
|
107
|
-
return i;
|
|
108
|
-
});
|
|
109
|
-
if (!status || status < Number(HttpStatusCodes.BAD_REQUEST)) {
|
|
110
|
-
status = errors[0].status;
|
|
111
|
-
if (status < Number(HttpStatusCodes.BAD_REQUEST))
|
|
112
|
-
status = HttpStatusCodes.INTERNAL_SERVER_ERROR;
|
|
113
|
-
}
|
|
114
|
-
const body = this.i18n.deep({
|
|
115
|
-
errors: errors.map(e => e.issue)
|
|
116
|
-
});
|
|
117
|
-
outgoing.statusCode = status;
|
|
118
|
-
outgoing.setHeader(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
119
|
-
outgoing.setHeader(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
120
|
-
outgoing.setHeader(HttpHeaderCodes.Pragma, 'no-cache');
|
|
121
|
-
outgoing.setHeader(HttpHeaderCodes.Expires, '-1');
|
|
122
|
-
outgoing.setHeader(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
123
|
-
outgoing.send(JSON.stringify(body));
|
|
124
|
-
outgoing.end();
|
|
125
|
-
}
|
|
126
|
-
log(logType, incoming, message, ...optionalParams) {
|
|
127
|
-
const logFn = logType === 'fatal'
|
|
128
|
-
? this.logger?.fatal || this.logger?.error
|
|
129
|
-
: this.logger?.[logType];
|
|
130
|
-
if (!logFn)
|
|
131
|
-
return;
|
|
132
|
-
logFn.apply(this.logger, [String(message), ...optionalParams]);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
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(',');
|
|
@@ -12,20 +12,20 @@ import path from 'path';
|
|
|
12
12
|
import { toString } from 'putil-varhelpers';
|
|
13
13
|
import vary from 'vary';
|
|
14
14
|
import { HttpStatusCodes, isStream, mergePrototype } from '@opra/common';
|
|
15
|
-
import { HttpOutgoingMessageHost } from './http-outgoing-message
|
|
15
|
+
import { HttpOutgoingMessageHost } from './impl/http-outgoing-message.host.js';
|
|
16
16
|
const charsetRegExp = /;\s*charset\s*=/;
|
|
17
17
|
function isHttpIncomingMessage(v) {
|
|
18
18
|
return v && typeof v.getHeaders === 'function' && isStream(v);
|
|
19
19
|
}
|
|
20
20
|
export var HttpServerResponse;
|
|
21
21
|
(function (HttpServerResponse) {
|
|
22
|
-
function
|
|
22
|
+
function from(instance) {
|
|
23
23
|
if (!isHttpIncomingMessage(instance))
|
|
24
|
-
instance = HttpOutgoingMessageHost
|
|
24
|
+
instance = new HttpOutgoingMessageHost(instance);
|
|
25
25
|
mergePrototype(instance, HttpServerResponseHost.prototype);
|
|
26
26
|
return instance;
|
|
27
27
|
}
|
|
28
|
-
HttpServerResponse.
|
|
28
|
+
HttpServerResponse.from = from;
|
|
29
29
|
})(HttpServerResponse || (HttpServerResponse = {}));
|
|
30
30
|
class HttpServerResponseHost {
|
|
31
31
|
attachment(filename) {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This file contains code blocks from open source NodeJs project
|
|
3
|
+
https://github.com/nodejs/
|
|
4
|
+
*/
|
|
5
|
+
import { Duplex, Readable } from 'stream';
|
|
6
|
+
import { HTTPParser } from '@browsery/http-parser';
|
|
7
|
+
import { isAsyncIterable, isIterable } from '@opra/common';
|
|
8
|
+
import { concatReadable } from '../helpers/concat-readable.js';
|
|
9
|
+
import { convertToHeaders, convertToHeadersDistinct } from '../helpers/convert-to-headers.js';
|
|
10
|
+
import { convertToRawHeaders } from '../helpers/convert-to-raw-headers.js';
|
|
11
|
+
export const CRLF = Buffer.from('\r\n');
|
|
12
|
+
export const kHeaders = Symbol.for('kHeaders');
|
|
13
|
+
export const kHeadersDistinct = Symbol.for('kHeadersDistinct');
|
|
14
|
+
export const kTrailers = Symbol.for('kTrailers');
|
|
15
|
+
export const kTrailersDistinct = Symbol.for('kTrailersDistinct');
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @class HttpIncomingMessageHost
|
|
19
|
+
*/
|
|
20
|
+
export class HttpIncomingMessageHost extends Duplex {
|
|
21
|
+
constructor(init) {
|
|
22
|
+
super();
|
|
23
|
+
this.rawHeaders = [];
|
|
24
|
+
this.rawTrailers = [];
|
|
25
|
+
this.complete = false;
|
|
26
|
+
this.joinDuplicateHeaders = false;
|
|
27
|
+
if (init) {
|
|
28
|
+
this.complete = true;
|
|
29
|
+
this.httpVersionMajor = init.httpVersionMajor || 1;
|
|
30
|
+
this.httpVersionMinor = init.httpVersionMinor || 0;
|
|
31
|
+
this.method = (init.method || 'GET').toUpperCase();
|
|
32
|
+
this.url = init.url || '';
|
|
33
|
+
if (init.body != null) {
|
|
34
|
+
if (Buffer.isBuffer(init.body))
|
|
35
|
+
this.body = init.body;
|
|
36
|
+
else if (typeof init.body === 'string')
|
|
37
|
+
this.body = Buffer.from(init.body, 'utf-8');
|
|
38
|
+
else
|
|
39
|
+
this.body = Buffer.from(JSON.stringify(init.body), 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
if (init.headers)
|
|
42
|
+
this.rawHeaders = Array.isArray(init.headers) ? init.headers : convertToRawHeaders(init.headers);
|
|
43
|
+
if (init.trailers)
|
|
44
|
+
this.rawTrailers = Array.isArray(init.trailers) ? init.trailers : convertToRawHeaders(init.trailers);
|
|
45
|
+
this.ip = init.ip || '';
|
|
46
|
+
this.ips = init.ips || (this.ip ? [this.ip] : []);
|
|
47
|
+
if (this.body && !this.headers['content-length'])
|
|
48
|
+
this.headers['content-length'] = String(this.body.length);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
get httpVersion() {
|
|
52
|
+
return this.httpVersionMajor
|
|
53
|
+
? this.httpVersionMajor + '.' + this.httpVersionMinor
|
|
54
|
+
: '';
|
|
55
|
+
}
|
|
56
|
+
get headers() {
|
|
57
|
+
if (!this[kHeaders])
|
|
58
|
+
this[kHeaders] = convertToHeaders(this.rawHeaders, {}, this.joinDuplicateHeaders);
|
|
59
|
+
return this[kHeaders];
|
|
60
|
+
}
|
|
61
|
+
set headers(headers) {
|
|
62
|
+
this[kHeaders] = headers;
|
|
63
|
+
}
|
|
64
|
+
get headersDistinct() {
|
|
65
|
+
if (!this[kHeadersDistinct])
|
|
66
|
+
this[kHeadersDistinct] = convertToHeadersDistinct(this.rawHeaders, {});
|
|
67
|
+
return this[kHeadersDistinct];
|
|
68
|
+
}
|
|
69
|
+
get trailers() {
|
|
70
|
+
if (!this[kTrailers])
|
|
71
|
+
this[kTrailers] = convertToHeaders(this.rawTrailers, {}, this.joinDuplicateHeaders);
|
|
72
|
+
return this[kTrailers];
|
|
73
|
+
}
|
|
74
|
+
set trailers(trailers) {
|
|
75
|
+
this[kTrailers] = trailers;
|
|
76
|
+
}
|
|
77
|
+
get trailersDistinct() {
|
|
78
|
+
if (!this[kTrailersDistinct])
|
|
79
|
+
this[kTrailersDistinct] = convertToHeadersDistinct(this.rawTrailers, {});
|
|
80
|
+
return this[kTrailersDistinct];
|
|
81
|
+
}
|
|
82
|
+
_read(size) {
|
|
83
|
+
if (!this.body) {
|
|
84
|
+
this.push(null);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!this._readStream) {
|
|
88
|
+
if (isIterable(this.body) || isAsyncIterable(this.body))
|
|
89
|
+
this._readStream = Readable.from(this.body);
|
|
90
|
+
else if (typeof this.body === 'string') {
|
|
91
|
+
this._readStream = Readable.from(Buffer.from(this.body, 'utf-8'));
|
|
92
|
+
}
|
|
93
|
+
else
|
|
94
|
+
this._readStream = Readable.from(Buffer.from(JSON.stringify(this.body), 'utf-8'));
|
|
95
|
+
}
|
|
96
|
+
const chunk = this._readStream.read(size);
|
|
97
|
+
this.push(chunk);
|
|
98
|
+
// this.push(null);s
|
|
99
|
+
}
|
|
100
|
+
_write(chunk, encoding, callback) {
|
|
101
|
+
const error = this._httpParser?.execute(chunk);
|
|
102
|
+
if (error && typeof error === 'object')
|
|
103
|
+
callback(error);
|
|
104
|
+
else
|
|
105
|
+
callback();
|
|
106
|
+
}
|
|
107
|
+
static from(iterable) {
|
|
108
|
+
if (typeof iterable === 'object' && !(isIterable(iterable) || isAsyncIterable(iterable)))
|
|
109
|
+
return new HttpIncomingMessageHost(iterable);
|
|
110
|
+
const msg = new HttpIncomingMessageHost();
|
|
111
|
+
const parser = msg._httpParser = new HTTPParser(HTTPParser.REQUEST);
|
|
112
|
+
let bodyChunks;
|
|
113
|
+
parser[HTTPParser.kOnHeadersComplete] = (info) => {
|
|
114
|
+
msg.httpVersionMajor = info.versionMajor;
|
|
115
|
+
msg.httpVersionMinor = info.versionMinor;
|
|
116
|
+
msg.rawHeaders = info.headers;
|
|
117
|
+
msg.method = HTTPParser.methods[info.method];
|
|
118
|
+
msg.url = info.url;
|
|
119
|
+
};
|
|
120
|
+
parser[HTTPParser.kOnHeaders] = (trailers) => {
|
|
121
|
+
msg.rawTrailers = trailers;
|
|
122
|
+
};
|
|
123
|
+
parser[HTTPParser.kOnBody] = (chunk, offset, length) => {
|
|
124
|
+
bodyChunks = bodyChunks || [];
|
|
125
|
+
bodyChunks.push(chunk.subarray(offset, offset + length));
|
|
126
|
+
};
|
|
127
|
+
parser[HTTPParser.kOnMessageComplete] = () => {
|
|
128
|
+
msg.complete = true;
|
|
129
|
+
if (bodyChunks)
|
|
130
|
+
msg.body = Buffer.concat(bodyChunks);
|
|
131
|
+
};
|
|
132
|
+
const readable = concatReadable(Readable.from(iterable), Readable.from(CRLF));
|
|
133
|
+
msg.once('finish', () => parser.finish());
|
|
134
|
+
readable.pipe(msg);
|
|
135
|
+
return msg;
|
|
136
|
+
}
|
|
137
|
+
static async fromAsync(iterable) {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const msg = this.from(iterable);
|
|
140
|
+
msg.once('finish', () => resolve(msg));
|
|
141
|
+
msg.once('error', (error) => reject(error));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
package/esm/adapter/http/impl/{http-outgoing-message-host.js → http-outgoing-message.host.js}
RENAMED
|
@@ -2,33 +2,40 @@
|
|
|
2
2
|
This file contains code blocks from open source NodeJs project
|
|
3
3
|
https://github.com/nodejs/
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import * as stream from 'stream';
|
|
5
|
+
import { Duplex } from 'stream';
|
|
7
6
|
import { validateHeaderName, validateHeaderValue, validateString } from '../helpers/common.js';
|
|
8
|
-
export const kOutHeaders = Symbol('kOutHeaders');
|
|
9
|
-
export const kOutTrailers = Symbol('kOutTrailers');
|
|
10
|
-
export const kErrored = Symbol('kErrored');
|
|
7
|
+
export const kOutHeaders = Symbol.for('kOutHeaders');
|
|
8
|
+
export const kOutTrailers = Symbol.for('kOutTrailers');
|
|
11
9
|
// noinspection JSUnusedLocalSymbols
|
|
12
10
|
/**
|
|
13
11
|
*
|
|
14
12
|
* @class HttpOutgoingMessageHost
|
|
15
13
|
*/
|
|
16
|
-
export class HttpOutgoingMessageHost {
|
|
17
|
-
constructor() {
|
|
14
|
+
export class HttpOutgoingMessageHost extends Duplex {
|
|
15
|
+
constructor(init) {
|
|
16
|
+
super();
|
|
18
17
|
this._headersSent = false;
|
|
19
|
-
this._closed = false;
|
|
20
|
-
this[_a] = null;
|
|
21
18
|
this.finished = false;
|
|
22
|
-
|
|
19
|
+
if (init) {
|
|
20
|
+
this.req = init.req;
|
|
21
|
+
this.statusCode = init?.statusCode || 0;
|
|
22
|
+
this.statusMessage = init?.statusMessage || '';
|
|
23
|
+
this.chunkedEncoding = !!init?.chunkedEncoding;
|
|
24
|
+
this.sendDate = !!init?.sendDate;
|
|
25
|
+
this.strictContentLength = !!init?.strictContentLength;
|
|
26
|
+
if (init.headers)
|
|
27
|
+
this.setHeaders(Array.isArray(init.headers) ? new Map([init.headers]) : init.headers);
|
|
28
|
+
this.body = init.body;
|
|
29
|
+
}
|
|
23
30
|
}
|
|
24
|
-
get
|
|
25
|
-
return this.
|
|
31
|
+
get httpVersionMajor() {
|
|
32
|
+
return this.req?.httpVersionMajor || this._httpVersionMajor;
|
|
26
33
|
}
|
|
27
|
-
get
|
|
28
|
-
return this.
|
|
34
|
+
get httpVersionMinor() {
|
|
35
|
+
return this.req?.httpVersionMinor || this._httpVersionMinor;
|
|
29
36
|
}
|
|
30
|
-
get
|
|
31
|
-
return this
|
|
37
|
+
get headersSent() {
|
|
38
|
+
return this._headersSent;
|
|
32
39
|
}
|
|
33
40
|
appendHeader(name, value) {
|
|
34
41
|
if (this.headersSent)
|
|
@@ -181,25 +188,7 @@ export class HttpOutgoingMessageHost {
|
|
|
181
188
|
//
|
|
182
189
|
return this;
|
|
183
190
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
this.statusCode = init?.statusCode || 0;
|
|
187
|
-
this.statusMessage = init?.statusMessage || '';
|
|
188
|
-
this.chunkedEncoding = !!init?.chunkedEncoding;
|
|
189
|
-
this.sendDate = !!init?.sendDate;
|
|
190
|
-
this.strictContentLength = !!init?.strictContentLength;
|
|
191
|
-
if (init.headers) {
|
|
192
|
-
this.setHeaders(Array.isArray(init.headers) ? new Map([init.headers]) : init.headers);
|
|
193
|
-
}
|
|
194
|
-
this.body = init.body;
|
|
195
|
-
}
|
|
196
|
-
static create(init) {
|
|
197
|
-
const msg = new HttpOutgoingMessageHost();
|
|
198
|
-
if (init)
|
|
199
|
-
msg._readConfig(init);
|
|
200
|
-
return msg;
|
|
191
|
+
static from(init) {
|
|
192
|
+
return new HttpOutgoingMessageHost(init);
|
|
201
193
|
}
|
|
202
194
|
}
|
|
203
|
-
_a = kErrored;
|
|
204
|
-
// Apply mixins
|
|
205
|
-
Object.assign(HttpOutgoingMessageHost.prototype, stream.Stream.prototype);
|