@opra/core 0.33.13 → 1.0.0-alpha.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/augmentation/18n.augmentation.js +17 -4
- package/cjs/augmentation/http-controller.augmentation.js +25 -0
- package/cjs/constants.js +5 -0
- package/cjs/execution-context.js +25 -12
- package/cjs/{services → helpers}/logger.js +1 -2
- package/cjs/{services/api-service.js → helpers/service-base.js} +8 -8
- package/cjs/http/express-adapter.js +164 -0
- package/cjs/http/http-adapter.js +27 -0
- package/cjs/http/http-context.js +116 -0
- package/cjs/http/impl/asset-cache.js +21 -0
- package/cjs/http/impl/http-handler.js +575 -0
- package/cjs/http/{http-server-request.js → impl/http-incoming.host.js} +21 -46
- package/cjs/http/{http-server-response.js → impl/http-outgoing.host.js} +7 -26
- package/cjs/http/{helpers/multipart-helper.js → impl/multipart-reader.js} +24 -22
- package/cjs/http/impl/{http-incoming-message.host.js → node-incoming-message.host.js} +13 -54
- package/cjs/http/impl/{http-outgoing-message.host.js → node-outgoing-message.host.js} +11 -14
- package/cjs/http/interfaces/http-incoming.interface.js +25 -0
- package/cjs/http/interfaces/http-outgoing.interface.js +22 -0
- package/cjs/http/interfaces/node-incoming-message.interface.js +63 -0
- package/cjs/http/interfaces/node-outgoing-message.interface.js +15 -0
- package/cjs/http/utils/body-reader.js +215 -0
- package/cjs/http/{helpers → utils}/convert-to-raw-headers.js +1 -2
- package/cjs/http/{helpers → utils}/match-known-fields.js +11 -9
- package/cjs/http/utils/wrap-exception.js +34 -0
- package/cjs/index.js +25 -25
- package/cjs/platform-adapter.js +21 -0
- package/cjs/type-guards.js +23 -0
- package/esm/augmentation/18n.augmentation.js +20 -7
- package/esm/augmentation/http-controller.augmentation.js +23 -0
- package/esm/constants.js +2 -0
- package/esm/execution-context.js +25 -13
- package/esm/{services → helpers}/logger.js +1 -2
- package/esm/{services/api-service.js → helpers/service-base.js} +6 -6
- package/esm/http/express-adapter.js +159 -0
- package/esm/http/http-adapter.js +23 -0
- package/esm/http/http-context.js +111 -0
- package/esm/http/impl/asset-cache.js +17 -0
- package/esm/http/impl/http-handler.js +570 -0
- package/esm/http/{http-server-request.js → impl/http-incoming.host.js} +17 -43
- package/esm/http/{http-server-response.js → impl/http-outgoing.host.js} +6 -26
- package/esm/http/{helpers/multipart-helper.js → impl/multipart-reader.js} +22 -20
- package/esm/http/impl/{http-incoming-message.host.js → node-incoming-message.host.js} +11 -52
- package/esm/http/impl/{http-outgoing-message.host.js → node-outgoing-message.host.js} +9 -12
- package/esm/http/interfaces/http-incoming.interface.js +22 -0
- package/esm/http/interfaces/http-outgoing.interface.js +19 -0
- package/esm/http/interfaces/node-incoming-message.interface.js +60 -0
- package/esm/http/interfaces/node-outgoing-message.interface.js +12 -0
- package/esm/http/utils/body-reader.js +210 -0
- package/esm/http/{helpers → utils}/convert-to-headers.js +1 -1
- package/esm/http/{helpers → utils}/convert-to-raw-headers.js +2 -3
- package/esm/http/{helpers → utils}/match-known-fields.js +11 -9
- package/esm/http/utils/wrap-exception.js +30 -0
- package/esm/index.js +25 -26
- package/esm/platform-adapter.js +19 -1
- package/esm/type-guards.js +16 -0
- package/package.json +21 -9
- package/types/augmentation/18n.augmentation.d.ts +32 -2
- package/types/augmentation/http-controller.augmentation.d.ts +21 -0
- package/types/constants.d.ts +2 -0
- package/types/execution-context.d.ts +24 -26
- package/types/helpers/service-base.d.ts +10 -0
- package/types/http/express-adapter.d.ts +13 -0
- package/types/http/http-adapter.d.ts +27 -0
- package/types/http/http-context.d.ts +44 -0
- package/types/http/impl/asset-cache.d.ts +5 -0
- package/types/http/impl/http-handler.d.ts +73 -0
- package/types/http/impl/http-incoming.host.d.ts +23 -0
- package/types/http/impl/http-outgoing.host.d.ts +17 -0
- package/types/http/{helpers/multipart-helper.d.ts → impl/multipart-reader.d.ts} +8 -6
- package/types/http/impl/{http-incoming-message.host.d.ts → node-incoming-message.host.d.ts} +9 -22
- package/types/http/impl/{http-outgoing-message.host.d.ts → node-outgoing-message.host.d.ts} +11 -27
- package/types/http/{http-server-request.d.ts → interfaces/http-incoming.interface.d.ts} +28 -17
- package/types/http/{http-server-response.d.ts → interfaces/http-outgoing.interface.d.ts} +17 -10
- package/types/http/interfaces/node-incoming-message.interface.d.ts +38 -0
- package/types/http/interfaces/node-outgoing-message.interface.d.ts +29 -0
- package/types/http/utils/body-reader.d.ts +41 -0
- package/types/http/utils/wrap-exception.d.ts +2 -0
- package/types/index.d.ts +24 -26
- package/types/platform-adapter.d.ts +20 -48
- package/types/type-guards.d.ts +8 -0
- package/cjs/augmentation/collection.augmentation.js +0 -2
- package/cjs/augmentation/container.augmentation.js +0 -2
- package/cjs/augmentation/resource.augmentation.js +0 -26
- package/cjs/augmentation/singleton.augmentation.js +0 -2
- package/cjs/augmentation/storage.augmentation.js +0 -2
- package/cjs/execution-context.host.js +0 -46
- package/cjs/http/adapters/express-adapter.host.js +0 -34
- package/cjs/http/adapters/express-adapter.js +0 -14
- package/cjs/http/adapters/node-http-adapter.host.js +0 -70
- package/cjs/http/adapters/node-http-adapter.js +0 -14
- package/cjs/http/helpers/json-body-loader.js +0 -29
- package/cjs/http/helpers/query-parsers.js +0 -16
- package/cjs/http/http-adapter-host.js +0 -715
- package/cjs/interfaces/interceptor.interface.js +0 -2
- package/cjs/interfaces/request-handler.interface.js +0 -2
- package/cjs/platform-adapter.host.js +0 -154
- package/cjs/request-context.js +0 -25
- package/cjs/request.host.js +0 -24
- package/cjs/request.js +0 -2
- package/cjs/response.host.js +0 -22
- package/cjs/response.js +0 -2
- package/esm/augmentation/collection.augmentation.js +0 -1
- package/esm/augmentation/container.augmentation.js +0 -1
- package/esm/augmentation/resource.augmentation.js +0 -24
- package/esm/augmentation/singleton.augmentation.js +0 -1
- package/esm/augmentation/storage.augmentation.js +0 -1
- package/esm/execution-context.host.js +0 -42
- package/esm/http/adapters/express-adapter.host.js +0 -30
- package/esm/http/adapters/express-adapter.js +0 -11
- package/esm/http/adapters/node-http-adapter.host.js +0 -65
- package/esm/http/adapters/node-http-adapter.js +0 -11
- package/esm/http/helpers/json-body-loader.js +0 -24
- package/esm/http/helpers/query-parsers.js +0 -12
- package/esm/http/http-adapter-host.js +0 -710
- package/esm/interfaces/interceptor.interface.js +0 -1
- package/esm/interfaces/request-handler.interface.js +0 -1
- package/esm/platform-adapter.host.js +0 -149
- package/esm/request-context.js +0 -22
- package/esm/request.host.js +0 -20
- package/esm/request.js +0 -1
- package/esm/response.host.js +0 -18
- package/esm/response.js +0 -1
- package/types/augmentation/collection.augmentation.d.ts +0 -146
- package/types/augmentation/container.augmentation.d.ts +0 -14
- package/types/augmentation/resource.augmentation.d.ts +0 -38
- package/types/augmentation/singleton.augmentation.d.ts +0 -83
- package/types/augmentation/storage.augmentation.d.ts +0 -50
- package/types/execution-context.host.d.ts +0 -25
- package/types/http/adapters/express-adapter.d.ts +0 -15
- package/types/http/adapters/express-adapter.host.d.ts +0 -12
- package/types/http/adapters/node-http-adapter.d.ts +0 -17
- package/types/http/adapters/node-http-adapter.host.d.ts +0 -19
- package/types/http/helpers/json-body-loader.d.ts +0 -5
- package/types/http/helpers/query-parsers.d.ts +0 -1
- package/types/http/http-adapter-host.d.ts +0 -34
- package/types/interfaces/interceptor.interface.d.ts +0 -2
- package/types/interfaces/request-handler.interface.d.ts +0 -4
- package/types/platform-adapter.host.d.ts +0 -43
- package/types/request-context.d.ts +0 -13
- package/types/request.d.ts +0 -14
- package/types/request.host.d.ts +0 -27
- package/types/response.d.ts +0 -22
- package/types/response.host.d.ts +0 -22
- package/types/services/api-service.d.ts +0 -10
- /package/cjs/http/{helpers → utils}/common.js +0 -0
- /package/cjs/http/{helpers → utils}/concat-readable.js +0 -0
- /package/cjs/http/{helpers → utils}/convert-to-headers.js +0 -0
- /package/esm/http/{helpers → utils}/common.js +0 -0
- /package/esm/http/{helpers → utils}/concat-readable.js +0 -0
- /package/types/{services → helpers}/logger.d.ts +0 -0
- /package/types/http/{helpers → utils}/common.d.ts +0 -0
- /package/types/http/{helpers → utils}/concat-readable.d.ts +0 -0
- /package/types/http/{helpers → utils}/convert-to-headers.d.ts +0 -0
- /package/types/http/{helpers → utils}/convert-to-raw-headers.d.ts +0 -0
- /package/types/http/{helpers → utils}/match-known-fields.d.ts +0 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpHandler = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const content_type_1 = require("content-type");
|
|
6
|
+
const fast_tokenizer_1 = require("fast-tokenizer");
|
|
7
|
+
const process = tslib_1.__importStar(require("node:process"));
|
|
8
|
+
const ts_gems_1 = require("ts-gems");
|
|
9
|
+
const valgen_1 = require("valgen");
|
|
10
|
+
const type_is_1 = tslib_1.__importDefault(require("@browsery/type-is"));
|
|
11
|
+
const common_1 = require("@opra/common");
|
|
12
|
+
const constants_js_1 = require("../../constants.js");
|
|
13
|
+
const wrap_exception_js_1 = require("../utils/wrap-exception.js");
|
|
14
|
+
/**
|
|
15
|
+
* @class HttpHandler
|
|
16
|
+
*/
|
|
17
|
+
class HttpHandler {
|
|
18
|
+
constructor(adapter) {
|
|
19
|
+
this.adapter = adapter;
|
|
20
|
+
this[constants_js_1.kAssetCache] = adapter[constants_js_1.kAssetCache];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Main http request handler
|
|
24
|
+
* @param context
|
|
25
|
+
* @protected
|
|
26
|
+
*/
|
|
27
|
+
async handleRequest(context) {
|
|
28
|
+
const { response } = context;
|
|
29
|
+
try {
|
|
30
|
+
response.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
|
|
31
|
+
// Expose headers if cors enabled
|
|
32
|
+
if (response.getHeader(common_1.HttpHeaderCodes.Access_Control_Allow_Origin)) {
|
|
33
|
+
// Expose X-Opra-* headers
|
|
34
|
+
response.appendHeader(common_1.HttpHeaderCodes.Access_Control_Expose_Headers, Object.values(common_1.HttpHeaderCodes).filter(k => k.toLowerCase().startsWith('x-opra-')));
|
|
35
|
+
}
|
|
36
|
+
// Parse request
|
|
37
|
+
try {
|
|
38
|
+
await this.parseRequest(context);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
if (e instanceof common_1.OpraException)
|
|
42
|
+
throw e;
|
|
43
|
+
if (e instanceof valgen_1.ValidationError) {
|
|
44
|
+
throw new common_1.BadRequestError({
|
|
45
|
+
message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
|
|
46
|
+
code: 'RESPONSE_VALIDATION',
|
|
47
|
+
details: e.issues,
|
|
48
|
+
}, e);
|
|
49
|
+
}
|
|
50
|
+
throw new common_1.BadRequestError(e);
|
|
51
|
+
}
|
|
52
|
+
await this.adapter.emitAsync('request', context);
|
|
53
|
+
// Call interceptors than execute request
|
|
54
|
+
if (this.adapter.interceptors) {
|
|
55
|
+
let i = 0;
|
|
56
|
+
const next = async () => {
|
|
57
|
+
const interceptor = this.adapter.interceptors[i++];
|
|
58
|
+
if (interceptor)
|
|
59
|
+
await interceptor(context, next);
|
|
60
|
+
await this._executeRequest(context);
|
|
61
|
+
};
|
|
62
|
+
await next();
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
await this._executeRequest(context);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
if (e instanceof valgen_1.ValidationError) {
|
|
69
|
+
e = new common_1.InternalServerError({
|
|
70
|
+
message: (0, common_1.translate)('error:RESPONSE_VALIDATION,', 'Response validation failed'),
|
|
71
|
+
code: 'RESPONSE_VALIDATION',
|
|
72
|
+
details: e.issues,
|
|
73
|
+
}, e);
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
e = (0, wrap_exception_js_1.wrapException)(e);
|
|
77
|
+
response.status(e.statusCode || e.status || common_1.HttpStatusCode.INTERNAL_SERVER_ERROR);
|
|
78
|
+
response.contentType(common_1.MimeTypes.opra_response_json);
|
|
79
|
+
await this._sendResponse(context, new common_1.OperationResult({ errors: [e] })).finally(() => {
|
|
80
|
+
if (!response.finished)
|
|
81
|
+
response.end();
|
|
82
|
+
});
|
|
83
|
+
// if (!outgoing.writableEnded) await this._sendErrorResponse(context.response, [error]);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await context.emitAsync('finish');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
*
|
|
91
|
+
* @param context
|
|
92
|
+
*/
|
|
93
|
+
async parseRequest(context) {
|
|
94
|
+
await this._parseParameters(context);
|
|
95
|
+
await this._parseContentType(context);
|
|
96
|
+
if (context.operation.requestBody?.immediateFetch)
|
|
97
|
+
await context.getBody();
|
|
98
|
+
/** Set default status code as the first status code between 200 and 299 */
|
|
99
|
+
if (context.operation) {
|
|
100
|
+
for (const r of context.operation.responses) {
|
|
101
|
+
const st = r.statusCode.find(sc => sc.start <= 299 && sc.end >= 200);
|
|
102
|
+
if (st) {
|
|
103
|
+
context.response.status(st.start);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
*
|
|
111
|
+
* @param context
|
|
112
|
+
* @protected
|
|
113
|
+
*/
|
|
114
|
+
async _parseParameters(context) {
|
|
115
|
+
const { operation, request } = context;
|
|
116
|
+
let prmName = '';
|
|
117
|
+
try {
|
|
118
|
+
const onFail = (issue) => {
|
|
119
|
+
issue.location = prmName;
|
|
120
|
+
return issue;
|
|
121
|
+
};
|
|
122
|
+
/** prepare decoders */
|
|
123
|
+
const getDecoder = (prm) => {
|
|
124
|
+
let decode = this[constants_js_1.kAssetCache].get(prm, 'decode');
|
|
125
|
+
if (!decode) {
|
|
126
|
+
decode = prm.type?.generateCodec('decode') || valgen_1.vg.isAny();
|
|
127
|
+
this[constants_js_1.kAssetCache].set(prm, 'decode', decode);
|
|
128
|
+
}
|
|
129
|
+
return decode;
|
|
130
|
+
};
|
|
131
|
+
const paramsLeft = new Set([...operation.parameters, ...operation.owner.parameters]);
|
|
132
|
+
/** parse cookie parameters */
|
|
133
|
+
if (request.cookies) {
|
|
134
|
+
for (prmName of Object.keys(request.cookies)) {
|
|
135
|
+
const oprPrm = operation.findParameter(prmName, 'cookie');
|
|
136
|
+
const cntPrm = operation.owner.findParameter(prmName, 'cookie');
|
|
137
|
+
const prm = oprPrm || cntPrm;
|
|
138
|
+
if (!prm)
|
|
139
|
+
continue;
|
|
140
|
+
if (oprPrm)
|
|
141
|
+
paramsLeft.delete(oprPrm);
|
|
142
|
+
if (cntPrm)
|
|
143
|
+
paramsLeft.delete(cntPrm);
|
|
144
|
+
const decode = getDecoder(prm);
|
|
145
|
+
const v = decode(request.cookies[prmName], { coerce: true, label: prmName, onFail });
|
|
146
|
+
if (v !== undefined)
|
|
147
|
+
context.cookies[prmName] = v;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** parse headers */
|
|
151
|
+
if (request.headers) {
|
|
152
|
+
for (prmName of Object.keys(request.headers)) {
|
|
153
|
+
const oprPrm = operation.findParameter(prmName, 'header');
|
|
154
|
+
const cntPrm = operation.owner.findParameter(prmName, 'header');
|
|
155
|
+
const prm = oprPrm || cntPrm;
|
|
156
|
+
if (!prm)
|
|
157
|
+
continue;
|
|
158
|
+
if (oprPrm)
|
|
159
|
+
paramsLeft.delete(oprPrm);
|
|
160
|
+
if (cntPrm)
|
|
161
|
+
paramsLeft.delete(cntPrm);
|
|
162
|
+
const decode = getDecoder(prm);
|
|
163
|
+
const v = decode(request.headers[prmName], { coerce: true, label: prmName, onFail });
|
|
164
|
+
if (v !== undefined)
|
|
165
|
+
context.headers[prmName] = v;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** parse path parameters */
|
|
169
|
+
if (request.params) {
|
|
170
|
+
for (prmName of Object.keys(request.params)) {
|
|
171
|
+
const oprPrm = operation.findParameter(prmName, 'path');
|
|
172
|
+
const cntPrm = operation.owner.findParameter(prmName, 'path');
|
|
173
|
+
const prm = oprPrm || cntPrm;
|
|
174
|
+
if (!prm)
|
|
175
|
+
continue;
|
|
176
|
+
if (oprPrm)
|
|
177
|
+
paramsLeft.delete(oprPrm);
|
|
178
|
+
if (cntPrm)
|
|
179
|
+
paramsLeft.delete(cntPrm);
|
|
180
|
+
const decode = getDecoder(prm);
|
|
181
|
+
const v = decode(request.params[prmName], { coerce: true, label: prmName, onFail });
|
|
182
|
+
if (v !== undefined)
|
|
183
|
+
context.pathParams[prmName] = v;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** parse query parameters */
|
|
187
|
+
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
188
|
+
const { searchParams } = url;
|
|
189
|
+
for (prmName of searchParams.keys()) {
|
|
190
|
+
const oprPrm = operation.findParameter(prmName, 'query');
|
|
191
|
+
const cntPrm = operation.owner.findParameter(prmName, 'query');
|
|
192
|
+
const prm = oprPrm || cntPrm;
|
|
193
|
+
if (!prm)
|
|
194
|
+
continue;
|
|
195
|
+
if (oprPrm)
|
|
196
|
+
paramsLeft.delete(oprPrm);
|
|
197
|
+
if (cntPrm)
|
|
198
|
+
paramsLeft.delete(cntPrm);
|
|
199
|
+
const decode = getDecoder(prm);
|
|
200
|
+
let values = searchParams?.getAll(prmName);
|
|
201
|
+
if (values?.length && prm.isArray) {
|
|
202
|
+
values = values.map(v => (0, fast_tokenizer_1.splitString)(v, { delimiters: prm.arraySeparator, quotes: true })).flat();
|
|
203
|
+
values = values.map(v => decode(v, { coerce: true, label: prmName, onFail }));
|
|
204
|
+
if (values.length)
|
|
205
|
+
context.queryParams[prmName] = values;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const v = decode(values[0], { coerce: true, label: prmName, onFail });
|
|
209
|
+
if (values.length)
|
|
210
|
+
context.queryParams[prmName] = v;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const prm of paramsLeft) {
|
|
214
|
+
// Throw error for required parameters
|
|
215
|
+
if (prm.required) {
|
|
216
|
+
const decode = getDecoder(prm);
|
|
217
|
+
decode(undefined, { coerce: true, label: String(prm.name), onFail });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
if (e instanceof valgen_1.ValidationError) {
|
|
223
|
+
e = new common_1.BadRequestError({
|
|
224
|
+
message: `Invalid parameter (${prmName}) value. ` + e.message,
|
|
225
|
+
code: 'REQUEST_VALIDATION',
|
|
226
|
+
details: e.issues,
|
|
227
|
+
}, e);
|
|
228
|
+
}
|
|
229
|
+
throw e;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
*
|
|
234
|
+
* @param context
|
|
235
|
+
* @protected
|
|
236
|
+
*/
|
|
237
|
+
async _parseContentType(context) {
|
|
238
|
+
const { request, operation } = context;
|
|
239
|
+
if (operation.requestBody?.content.length) {
|
|
240
|
+
let mediaType;
|
|
241
|
+
let contentType = request.header('content-type');
|
|
242
|
+
if (contentType) {
|
|
243
|
+
contentType = (0, content_type_1.parse)(contentType).type;
|
|
244
|
+
mediaType = operation.requestBody.content.find(mc => {
|
|
245
|
+
return (mc.contentType && type_is_1.default.is(contentType, Array.isArray(mc.contentType) ? mc.contentType : [mc.contentType]));
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (!mediaType) {
|
|
249
|
+
const contentTypes = operation.requestBody.content.map(mc => mc.contentType).flat();
|
|
250
|
+
throw new common_1.BadRequestError(`Request body should be one of required content types (${contentTypes.join(', ')})`);
|
|
251
|
+
}
|
|
252
|
+
(0, ts_gems_1.asMutable)(context).mediaType = mediaType;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
*
|
|
257
|
+
* @param context
|
|
258
|
+
* @protected
|
|
259
|
+
*/
|
|
260
|
+
async _executeRequest(context) {
|
|
261
|
+
if (!context.operationHandler)
|
|
262
|
+
throw new common_1.MethodNotAllowedError();
|
|
263
|
+
const responseValue = await context.operationHandler.call(context.controllerInstance, context);
|
|
264
|
+
const { response } = context;
|
|
265
|
+
if (!response.writableEnded)
|
|
266
|
+
await this._sendResponse(context, responseValue).finally(() => {
|
|
267
|
+
if (!response.writableEnded)
|
|
268
|
+
response.end();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
*
|
|
273
|
+
* @param context
|
|
274
|
+
* @param responseValue
|
|
275
|
+
* @protected
|
|
276
|
+
*/
|
|
277
|
+
async _sendResponse(context, responseValue) {
|
|
278
|
+
const { response } = context;
|
|
279
|
+
const { document } = this.adapter;
|
|
280
|
+
const responseArgs = this._determineResponseArgs(context, responseValue);
|
|
281
|
+
const { operationResponse, statusCode } = responseArgs;
|
|
282
|
+
let { contentType, body } = responseArgs;
|
|
283
|
+
const operationResultType = document.node.getDataType(common_1.OperationResult);
|
|
284
|
+
let operationResultEncoder = this[constants_js_1.kAssetCache].get(operationResultType, 'encode');
|
|
285
|
+
if (!operationResultEncoder) {
|
|
286
|
+
operationResultEncoder = operationResultType.generateCodec('encode');
|
|
287
|
+
this[constants_js_1.kAssetCache].set(operationResultType, 'encode', operationResultEncoder);
|
|
288
|
+
}
|
|
289
|
+
/** Validate response */
|
|
290
|
+
if (operationResponse?.type) {
|
|
291
|
+
if (!(body == null && statusCode === common_1.HttpStatusCode.NO_CONTENT)) {
|
|
292
|
+
/** Generate encoder */
|
|
293
|
+
let encode = this[constants_js_1.kAssetCache].get(operationResponse, 'encode');
|
|
294
|
+
if (!encode) {
|
|
295
|
+
encode = operationResponse.type.generateCodec('encode', {
|
|
296
|
+
partial: operationResponse.partial,
|
|
297
|
+
projection: '*',
|
|
298
|
+
});
|
|
299
|
+
if (operationResponse) {
|
|
300
|
+
if (operationResponse.isArray)
|
|
301
|
+
encode = valgen_1.vg.isArray(encode);
|
|
302
|
+
this[constants_js_1.kAssetCache].set(operationResponse, 'encode', encode);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/** Encode body */
|
|
306
|
+
if (operationResponse.type.extendsFrom(operationResultType)) {
|
|
307
|
+
if (body instanceof common_1.OperationResult)
|
|
308
|
+
body = encode(body);
|
|
309
|
+
else {
|
|
310
|
+
body.payload = encode(body.payload);
|
|
311
|
+
body = operationResultEncoder(body);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
if (body instanceof common_1.OperationResult &&
|
|
316
|
+
contentType &&
|
|
317
|
+
type_is_1.default.is(contentType, [common_1.MimeTypes.opra_response_json])) {
|
|
318
|
+
body.payload = encode(body.payload);
|
|
319
|
+
body = operationResultEncoder(body);
|
|
320
|
+
}
|
|
321
|
+
else
|
|
322
|
+
body = encode(body);
|
|
323
|
+
}
|
|
324
|
+
if (body instanceof common_1.OperationResult && operationResponse.type) {
|
|
325
|
+
body.type = operationResponse.type.name ? operationResponse.type.name : '#embedded';
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else if (body != null) {
|
|
330
|
+
if (body instanceof common_1.OperationResult) {
|
|
331
|
+
body = operationResultEncoder(body);
|
|
332
|
+
contentType = common_1.MimeTypes.opra_response_json;
|
|
333
|
+
}
|
|
334
|
+
else if (Buffer.isBuffer(body))
|
|
335
|
+
contentType = common_1.MimeTypes.binary;
|
|
336
|
+
else if (typeof body === 'object') {
|
|
337
|
+
contentType = contentType || common_1.MimeTypes.json;
|
|
338
|
+
if (typeof body.toJSON === 'function')
|
|
339
|
+
body = body.toJSON();
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
contentType = contentType || common_1.MimeTypes.text;
|
|
343
|
+
body = String(body);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/** Set content-type header value if not set */
|
|
347
|
+
if (contentType && contentType !== responseArgs.contentType)
|
|
348
|
+
response.setHeader('content-type', contentType);
|
|
349
|
+
response.status(statusCode);
|
|
350
|
+
if (body == null) {
|
|
351
|
+
response.end();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
let x;
|
|
355
|
+
if (Buffer.isBuffer(body) || (0, common_1.isReadableStream)(body))
|
|
356
|
+
x = body;
|
|
357
|
+
else if ((0, common_1.isBlob)(body))
|
|
358
|
+
x = body.stream();
|
|
359
|
+
else if (typeof body === 'object')
|
|
360
|
+
x = JSON.stringify(body);
|
|
361
|
+
else
|
|
362
|
+
x = String(body);
|
|
363
|
+
response.end(x);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
*
|
|
367
|
+
* @param context
|
|
368
|
+
* @param body
|
|
369
|
+
* @protected
|
|
370
|
+
*/
|
|
371
|
+
_determineResponseArgs(context, body) {
|
|
372
|
+
const { response, operation } = context;
|
|
373
|
+
const hasBody = body != null;
|
|
374
|
+
const statusCode = !hasBody && response.statusCode === common_1.HttpStatusCode.OK ? common_1.HttpStatusCode.NO_CONTENT : response.statusCode;
|
|
375
|
+
/** Parse content-type header */
|
|
376
|
+
const parsedContentType = hasBody && response.hasHeader('content-type') ? (0, content_type_1.parse)(response) : undefined;
|
|
377
|
+
let contentType = parsedContentType?.type;
|
|
378
|
+
/** Estimate content type if not defined */
|
|
379
|
+
if (hasBody && !contentType) {
|
|
380
|
+
if (body instanceof common_1.OperationResult)
|
|
381
|
+
contentType = common_1.MimeTypes.opra_response_json;
|
|
382
|
+
else if (Buffer.isBuffer(body))
|
|
383
|
+
contentType = common_1.MimeTypes.binary;
|
|
384
|
+
}
|
|
385
|
+
let operationResponse;
|
|
386
|
+
const cacheKey = `HttpOperationResponse:${statusCode}${contentType ? ':' + contentType : ''}`;
|
|
387
|
+
let responseArgs = this[constants_js_1.kAssetCache].get(response, cacheKey);
|
|
388
|
+
if (!responseArgs) {
|
|
389
|
+
responseArgs = { statusCode, contentType };
|
|
390
|
+
if (operation.responses.length) {
|
|
391
|
+
/** Filter available HttpOperationResponse instances according to status code. */
|
|
392
|
+
const filteredResponses = operation.responses.filter(r => r.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode));
|
|
393
|
+
/** Throw InternalServerError if controller returns non-configured status code */
|
|
394
|
+
if (!filteredResponses.length && statusCode < 400)
|
|
395
|
+
throw new common_1.InternalServerError(`No responses defined for status code ${statusCode} in operation "${operation.name}"`);
|
|
396
|
+
/** We search for content-type in filtered HttpOperationResponse array */
|
|
397
|
+
if (filteredResponses.length) {
|
|
398
|
+
/** If no response returned, and content-type has not been set (No response wants to be returned by operation) */
|
|
399
|
+
if (!hasBody) {
|
|
400
|
+
/** Find HttpOperationResponse with no content-type */
|
|
401
|
+
operationResponse = filteredResponses.find(r => !r.contentType);
|
|
402
|
+
}
|
|
403
|
+
if (!operationResponse) {
|
|
404
|
+
/** Find HttpOperationResponse according to content-type */
|
|
405
|
+
if (contentType) {
|
|
406
|
+
// Find HttpEndpointResponse instance according to content-type header
|
|
407
|
+
operationResponse = filteredResponses.find(r => type_is_1.default.is(contentType, (0, valgen_1.toArray)(r.contentType)));
|
|
408
|
+
if (!operationResponse)
|
|
409
|
+
throw new common_1.InternalServerError(`Operation didn't configured to return "${contentType}" content`);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
/** Select first HttpOperationResponse if content-type header has not been set */
|
|
413
|
+
operationResponse = filteredResponses[0];
|
|
414
|
+
if (operationResponse.contentType) {
|
|
415
|
+
const ct = type_is_1.default.normalize(Array.isArray(operationResponse.contentType)
|
|
416
|
+
? operationResponse.contentType[0]
|
|
417
|
+
: operationResponse.contentType);
|
|
418
|
+
if (typeof ct === 'string')
|
|
419
|
+
responseArgs.contentType = contentType = ct;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
responseArgs.operationResponse = operationResponse;
|
|
424
|
+
if (!operationResponse.statusCode.find(sc => sc.start <= statusCode && sc.end >= statusCode)) {
|
|
425
|
+
responseArgs.statusCode = operationResponse.statusCode[0].start;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (!hasBody)
|
|
430
|
+
delete responseArgs.contentType;
|
|
431
|
+
this[constants_js_1.kAssetCache].set(response, cacheKey, { ...responseArgs });
|
|
432
|
+
}
|
|
433
|
+
/** Fix response value according to composition */
|
|
434
|
+
const composition = operationResponse?.owner.composition;
|
|
435
|
+
if (composition && body != null) {
|
|
436
|
+
switch (composition) {
|
|
437
|
+
case 'Entity.Create':
|
|
438
|
+
case 'Entity.Get':
|
|
439
|
+
case 'Entity.FindMany':
|
|
440
|
+
case 'Entity.Update': {
|
|
441
|
+
if (!(body instanceof common_1.OperationResult))
|
|
442
|
+
body = new common_1.OperationResult({
|
|
443
|
+
payload: body,
|
|
444
|
+
});
|
|
445
|
+
if ((composition === 'Entity.Create' || composition === 'Entity.Update') &&
|
|
446
|
+
composition &&
|
|
447
|
+
body.affected == null)
|
|
448
|
+
body.affected = 1;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case 'Entity.Delete':
|
|
452
|
+
case 'Entity.DeleteMany':
|
|
453
|
+
case 'Entity.UpdateMany': {
|
|
454
|
+
if (!(body instanceof common_1.OperationResult))
|
|
455
|
+
body = new common_1.OperationResult({
|
|
456
|
+
affected: body,
|
|
457
|
+
});
|
|
458
|
+
body.affected =
|
|
459
|
+
typeof body.affected === 'number'
|
|
460
|
+
? body.affected
|
|
461
|
+
: typeof body.affected === 'boolean'
|
|
462
|
+
? body.affected
|
|
463
|
+
? 1
|
|
464
|
+
: 0
|
|
465
|
+
: undefined;
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (responseArgs.contentType && responseArgs.contentType !== parsedContentType?.type)
|
|
471
|
+
response.setHeader('content-type', responseArgs.contentType);
|
|
472
|
+
if (responseArgs.contentType &&
|
|
473
|
+
body != null &&
|
|
474
|
+
!(body instanceof common_1.OperationResult) &&
|
|
475
|
+
type_is_1.default.is(responseArgs.contentType, [common_1.MimeTypes.opra_response_json]))
|
|
476
|
+
body = new common_1.OperationResult({ payload: body });
|
|
477
|
+
if (hasBody)
|
|
478
|
+
responseArgs.body = body;
|
|
479
|
+
return responseArgs;
|
|
480
|
+
}
|
|
481
|
+
async sendDocumentSchema(context) {
|
|
482
|
+
const { request, response } = context;
|
|
483
|
+
const { document } = this.adapter;
|
|
484
|
+
response.setHeader('content-type', common_1.MimeTypes.json);
|
|
485
|
+
/** Check if response cache exists */
|
|
486
|
+
let responseBody = this[constants_js_1.kAssetCache].get(document, '$schema-response');
|
|
487
|
+
/** Create response if response cache does not exists */
|
|
488
|
+
if (!responseBody) {
|
|
489
|
+
const url = new URL(request.originalUrl || request.url || '/', 'http://tempuri.org');
|
|
490
|
+
const { searchParams } = url;
|
|
491
|
+
// const nsPath = searchParams.get('ns');
|
|
492
|
+
// if (nsPath) {
|
|
493
|
+
// const arr = nsPath.split('/');
|
|
494
|
+
// let doc = document;
|
|
495
|
+
// for (const a of arr) {
|
|
496
|
+
// }
|
|
497
|
+
// }
|
|
498
|
+
const schema = document.export({ references: searchParams.get('references') });
|
|
499
|
+
const dt = document.node.getComplexType('OperationResult');
|
|
500
|
+
let encode = this[constants_js_1.kAssetCache].get(dt, 'encode');
|
|
501
|
+
if (!encode) {
|
|
502
|
+
encode = dt.generateCodec('encode');
|
|
503
|
+
this[constants_js_1.kAssetCache].set(dt, 'encode', encode);
|
|
504
|
+
}
|
|
505
|
+
responseBody = JSON.stringify(schema);
|
|
506
|
+
this[constants_js_1.kAssetCache].set(document, '$schema-response', responseBody);
|
|
507
|
+
}
|
|
508
|
+
response.end(responseBody);
|
|
509
|
+
}
|
|
510
|
+
async sendErrorResponse(response, errors) {
|
|
511
|
+
if (response.headersSent) {
|
|
512
|
+
response.end();
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (!errors.length)
|
|
516
|
+
errors.push((0, wrap_exception_js_1.wrapException)({ status: response.statusCode || 500 }));
|
|
517
|
+
const { logger } = this.adapter;
|
|
518
|
+
errors.forEach(x => {
|
|
519
|
+
if (x instanceof common_1.OpraException) {
|
|
520
|
+
switch (x.severity) {
|
|
521
|
+
case 'fatal':
|
|
522
|
+
logger.fatal(x);
|
|
523
|
+
break;
|
|
524
|
+
case 'warning':
|
|
525
|
+
logger.warn(x);
|
|
526
|
+
break;
|
|
527
|
+
default:
|
|
528
|
+
logger.error(x);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
else
|
|
532
|
+
logger.fatal(x);
|
|
533
|
+
});
|
|
534
|
+
const wrappedErrors = errors.map(wrap_exception_js_1.wrapException);
|
|
535
|
+
// Sort errors from fatal to info
|
|
536
|
+
wrappedErrors.sort((a, b) => {
|
|
537
|
+
const i = common_1.IssueSeverity.Keys.indexOf(a.severity) - common_1.IssueSeverity.Keys.indexOf(b.severity);
|
|
538
|
+
if (i === 0)
|
|
539
|
+
return b.status - a.status;
|
|
540
|
+
return i;
|
|
541
|
+
});
|
|
542
|
+
let status = response.statusCode || 0;
|
|
543
|
+
if (!status || status < Number(common_1.HttpStatusCode.BAD_REQUEST)) {
|
|
544
|
+
status = wrappedErrors[0].status;
|
|
545
|
+
if (status < Number(common_1.HttpStatusCode.BAD_REQUEST))
|
|
546
|
+
status = common_1.HttpStatusCode.INTERNAL_SERVER_ERROR;
|
|
547
|
+
}
|
|
548
|
+
response.statusCode = status;
|
|
549
|
+
const { document } = this.adapter;
|
|
550
|
+
const dt = document.node.getComplexType('OperationResult');
|
|
551
|
+
let encode = this[constants_js_1.kAssetCache].get(dt, 'encode');
|
|
552
|
+
if (!encode) {
|
|
553
|
+
encode = dt.generateCodec('encode');
|
|
554
|
+
this[constants_js_1.kAssetCache].set(dt, 'encode', encode);
|
|
555
|
+
}
|
|
556
|
+
const { i18n } = this.adapter;
|
|
557
|
+
const bodyObject = new common_1.OperationResult({
|
|
558
|
+
errors: wrappedErrors.map(x => {
|
|
559
|
+
const o = x.toJSON();
|
|
560
|
+
if (!(process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'development'))
|
|
561
|
+
delete o.stack;
|
|
562
|
+
return i18n.deep(o);
|
|
563
|
+
}),
|
|
564
|
+
});
|
|
565
|
+
const body = encode(bodyObject);
|
|
566
|
+
response.setHeader(common_1.HttpHeaderCodes.Content_Type, common_1.MimeTypes.opra_response_json + '; charset=utf-8');
|
|
567
|
+
response.setHeader(common_1.HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
568
|
+
response.setHeader(common_1.HttpHeaderCodes.Pragma, 'no-cache');
|
|
569
|
+
response.setHeader(common_1.HttpHeaderCodes.Expires, '-1');
|
|
570
|
+
response.setHeader(common_1.HttpHeaderCodes.X_Opra_Version, common_1.OpraSchema.SpecVersion);
|
|
571
|
+
response.send(JSON.stringify(body));
|
|
572
|
+
response.end();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
exports.HttpHandler = HttpHandler;
|
|
@@ -1,49 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpIncomingHost = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
2
5
|
/*
|
|
3
6
|
Some parts of this file contains codes from open source express library
|
|
4
7
|
https://github.com/expressjs
|
|
5
8
|
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.HttpServerRequest = void 0;
|
|
8
|
-
const tslib_1 = require("tslib");
|
|
9
9
|
const accepts_1 = tslib_1.__importDefault(require("accepts"));
|
|
10
10
|
const fresh_1 = tslib_1.__importDefault(require("fresh"));
|
|
11
11
|
const range_parser_1 = tslib_1.__importDefault(require("range-parser"));
|
|
12
12
|
const type_is_1 = tslib_1.__importDefault(require("@browsery/type-is"));
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
function isHttpIncomingMessage(v) {
|
|
16
|
-
return v && Array.isArray(v.rawHeaders) && (0, common_1.isReadable)(v);
|
|
17
|
-
}
|
|
18
|
-
var HttpServerRequest;
|
|
19
|
-
(function (HttpServerRequest) {
|
|
20
|
-
function from(instance) {
|
|
21
|
-
if (!isHttpIncomingMessage(instance))
|
|
22
|
-
instance = http_incoming_message_host_js_1.HttpIncomingMessageHost.from(instance);
|
|
23
|
-
(0, common_1.mergePrototype)(instance, HttpServerRequestHost.prototype);
|
|
24
|
-
const req = instance;
|
|
25
|
-
req.baseUrl = req.baseUrl || '';
|
|
26
|
-
req.parsedUrl = req.parsedUrl || new common_1.OpraURL(req.url);
|
|
27
|
-
if (!req.searchParams)
|
|
28
|
-
Object.defineProperty(req, 'searchParams', {
|
|
29
|
-
get() {
|
|
30
|
-
return req.parsedUrl.searchParams;
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
return req;
|
|
34
|
-
}
|
|
35
|
-
HttpServerRequest.from = from;
|
|
36
|
-
})(HttpServerRequest || (exports.HttpServerRequest = HttpServerRequest = {}));
|
|
37
|
-
class HttpServerRequestHost {
|
|
38
|
-
constructor() {
|
|
39
|
-
this.basePath = '/';
|
|
40
|
-
}
|
|
13
|
+
const body_reader_js_1 = require("../utils/body-reader.js");
|
|
14
|
+
class HttpIncomingHost {
|
|
41
15
|
get protocol() {
|
|
42
16
|
const proto = this.header('X-Forwarded-Proto') || 'http';
|
|
43
17
|
const index = proto.indexOf(',');
|
|
44
|
-
return index !== -1
|
|
45
|
-
? proto.substring(0, index).trim()
|
|
46
|
-
: proto.trim();
|
|
18
|
+
return index !== -1 ? proto.substring(0, index).trim() : proto.trim();
|
|
47
19
|
}
|
|
48
20
|
get secure() {
|
|
49
21
|
return this.protocol === 'https';
|
|
@@ -58,16 +30,13 @@ class HttpServerRequestHost {
|
|
|
58
30
|
// single value, but this is to be safe.
|
|
59
31
|
host = host.substring(0, host.indexOf(',')).trim();
|
|
60
32
|
}
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
? host.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return index !== -1
|
|
69
|
-
? host.substring(0, index)
|
|
70
|
-
: host;
|
|
33
|
+
if (host) {
|
|
34
|
+
// IPv6 literal support
|
|
35
|
+
const offset = host[0] === '[' ? host.indexOf(']') + 1 : 0;
|
|
36
|
+
const index = host.indexOf(':', offset);
|
|
37
|
+
return index !== -1 ? host.substring(0, index) : host;
|
|
38
|
+
}
|
|
39
|
+
return '';
|
|
71
40
|
}
|
|
72
41
|
get fresh() {
|
|
73
42
|
const method = this.method;
|
|
@@ -78,8 +47,8 @@ class HttpServerRequestHost {
|
|
|
78
47
|
// 2xx or 304 as per rfc2616 14.26
|
|
79
48
|
if ((status >= 200 && status < 300) || 304 === status) {
|
|
80
49
|
return (0, fresh_1.default)(this.headers, {
|
|
81
|
-
|
|
82
|
-
'last-modified': this.res.getHeader('Last-Modified')
|
|
50
|
+
etag: this.res.getHeader('ETag'),
|
|
51
|
+
'last-modified': this.res.getHeader('Last-Modified'),
|
|
83
52
|
});
|
|
84
53
|
}
|
|
85
54
|
return false;
|
|
@@ -134,4 +103,10 @@ class HttpServerRequestHost {
|
|
|
134
103
|
return;
|
|
135
104
|
return (0, range_parser_1.default)(size, range, options);
|
|
136
105
|
}
|
|
106
|
+
async readBody(options) {
|
|
107
|
+
if (!this.complete)
|
|
108
|
+
this.body = await body_reader_js_1.BodyReader.read(this, options);
|
|
109
|
+
return this.body;
|
|
110
|
+
}
|
|
137
111
|
}
|
|
112
|
+
exports.HttpIncomingHost = HttpIncomingHost;
|