@opra/core 0.20.3 → 0.22.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/adapter.js +2 -2
- package/cjs/adapter/http/express-adapter.js +29 -7
- package/cjs/adapter/http/helpers/common.js +66 -0
- package/cjs/adapter/http/helpers/convert-to-headers.js +65 -0
- package/cjs/adapter/http/helpers/convert-to-raw-headers.js +25 -0
- package/cjs/adapter/http/helpers/match-known-fields.js +47 -0
- package/cjs/adapter/http/http-adapter.js +82 -440
- package/cjs/adapter/http/impl/http-incoming-message-host.js +127 -0
- package/cjs/adapter/http/impl/http-outgoing-message-host.js +210 -0
- package/cjs/adapter/http/impl/http-server-request.js +125 -0
- package/cjs/adapter/http/impl/http-server-response.js +226 -0
- package/cjs/adapter/http/request-parsers/batch-request-parser.js +169 -0
- package/cjs/adapter/http/request-parsers/parse-collection-request.js +165 -0
- package/cjs/adapter/http/request-parsers/parse-request.js +24 -0
- package/cjs/adapter/http/request-parsers/parse-singleton-request.js +96 -0
- package/cjs/adapter/request-context.host.js +17 -3
- package/cjs/adapter/request.host.js +6 -3
- package/cjs/adapter/response.host.js +5 -3
- package/cjs/index.js +4 -2
- package/esm/adapter/adapter.js +2 -2
- package/esm/adapter/http/express-adapter.js +6 -6
- package/esm/adapter/http/helpers/common.js +60 -0
- package/esm/adapter/http/helpers/convert-to-headers.js +60 -0
- package/esm/adapter/http/helpers/convert-to-raw-headers.js +21 -0
- package/esm/adapter/http/helpers/match-known-fields.js +43 -0
- package/esm/adapter/http/http-adapter.js +83 -441
- package/esm/adapter/http/impl/http-incoming-message-host.js +122 -0
- package/esm/adapter/http/impl/http-outgoing-message-host.js +205 -0
- package/esm/adapter/http/impl/http-server-request.js +121 -0
- package/esm/adapter/http/impl/http-server-response.js +222 -0
- package/esm/adapter/http/request-parsers/batch-request-parser.js +169 -0
- package/esm/adapter/http/request-parsers/parse-collection-request.js +161 -0
- package/esm/adapter/http/request-parsers/parse-request.js +20 -0
- package/esm/adapter/http/request-parsers/parse-singleton-request.js +92 -0
- package/esm/adapter/request-context.host.js +17 -3
- package/esm/adapter/request.host.js +6 -3
- package/esm/adapter/response.host.js +5 -3
- package/esm/index.js +4 -2
- package/package.json +19 -15
- package/types/adapter/adapter.d.ts +1 -1
- package/types/adapter/http/helpers/common.d.ts +17 -0
- package/types/adapter/http/helpers/convert-to-headers.d.ts +2 -0
- package/types/adapter/http/helpers/convert-to-raw-headers.d.ts +3 -0
- package/types/adapter/http/helpers/match-known-fields.d.ts +6 -0
- package/types/adapter/http/http-adapter.d.ts +7 -12
- package/types/adapter/http/impl/http-incoming-message-host.d.ts +58 -0
- package/types/adapter/http/impl/http-outgoing-message-host.d.ts +72 -0
- package/types/adapter/http/{http-request-message.d.ts → impl/http-server-request.d.ts} +52 -85
- package/types/adapter/http/impl/http-server-response.d.ts +137 -0
- package/types/adapter/http/request-parsers/batch-request-parser.d.ts +0 -0
- package/types/adapter/http/request-parsers/parse-collection-request.d.ts +4 -0
- package/types/adapter/http/request-parsers/parse-request.d.ts +4 -0
- package/types/adapter/http/request-parsers/parse-singleton-request.d.ts +4 -0
- package/types/adapter/interfaces/request-context.interface.d.ts +14 -10
- package/types/adapter/interfaces/request.interface.d.ts +3 -2
- package/types/adapter/interfaces/response.interface.d.ts +2 -2
- package/types/adapter/request-context.host.d.ts +9 -6
- package/types/adapter/request.host.d.ts +8 -4
- package/types/adapter/response.host.d.ts +6 -4
- package/types/index.d.ts +4 -2
- package/cjs/adapter/http/http-message.host.js +0 -251
- package/cjs/adapter/http/http-request-context.host.js +0 -28
- package/cjs/adapter/http/http-request-message.js +0 -152
- package/cjs/adapter/http/http-request.host.js +0 -14
- package/cjs/adapter/http/http-response-message.js +0 -238
- package/cjs/adapter/http/http-response.host.js +0 -14
- package/esm/adapter/http/http-message.host.js +0 -246
- package/esm/adapter/http/http-request-context.host.js +0 -24
- package/esm/adapter/http/http-request-message.js +0 -148
- package/esm/adapter/http/http-request.host.js +0 -10
- package/esm/adapter/http/http-response-message.js +0 -233
- package/esm/adapter/http/http-response.host.js +0 -10
- package/types/adapter/http/http-message.host.d.ts +0 -122
- package/types/adapter/http/http-request-context.host.d.ts +0 -16
- package/types/adapter/http/http-request.host.d.ts +0 -7
- package/types/adapter/http/http-response-message.d.ts +0 -321
- package/types/adapter/http/http-response.host.d.ts +0 -7
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Task } from 'power-tasks';
|
|
2
|
-
import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity,
|
|
2
|
+
import { BadRequestError, Collection, HttpHeaderCodes, HttpStatusCodes, InternalServerError, isReadable, IssueSeverity, OpraException, OpraSchema, wrapException } from '@opra/common';
|
|
3
3
|
import { OpraAdapter } from '../adapter.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { RequestContextHost } from '../request-context.host.js';
|
|
5
|
+
import { ResponseHost } from '../response.host.js';
|
|
6
|
+
import { parseRequest } from './request-parsers/parse-request.js';
|
|
7
7
|
/**
|
|
8
8
|
*
|
|
9
9
|
* @class OpraHttpAdapter
|
|
@@ -17,43 +17,83 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
17
17
|
*/
|
|
18
18
|
async handler(incoming, outgoing) {
|
|
19
19
|
try {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
try {
|
|
21
|
+
const request = await parseRequest(this._apiRoot, incoming);
|
|
22
|
+
const task = this.createRequestTask(request, outgoing);
|
|
23
|
+
await task.toPromise();
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const task = new Task(async () => {
|
|
29
|
-
try {
|
|
30
|
-
await this.executeRequest(context);
|
|
31
|
-
if (request.operation === 'findMany' && request.args.count && response.count != null) {
|
|
32
|
-
response.switchToHttp().header(HttpHeaderCodes.X_Opra_Total_Matches, String(response.count));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
return this.errorHandler(incoming, outgoing, [error]);
|
|
37
|
-
}
|
|
38
|
-
await this.sendResponse(context);
|
|
39
|
-
}, {
|
|
40
|
-
id: incoming.get('content-id'),
|
|
41
|
-
exclusive: request.crud !== 'read'
|
|
42
|
-
});
|
|
43
|
-
await task.toPromise().catch(e => {
|
|
44
|
-
this.logger?.error?.(e);
|
|
45
|
-
outgoing.sendStatus(500);
|
|
46
|
-
});
|
|
47
|
-
await this.emitAsync('request-finish', context);
|
|
48
|
-
return;
|
|
25
|
+
catch (e) {
|
|
26
|
+
if (e instanceof OpraException)
|
|
27
|
+
throw e;
|
|
28
|
+
throw new BadRequestError(e);
|
|
49
29
|
}
|
|
50
|
-
throw new BadRequestError({ message: 'Unsupported Content-Type' });
|
|
51
30
|
}
|
|
52
31
|
catch (error) {
|
|
53
|
-
await this.
|
|
32
|
+
await this.handleError(incoming, outgoing, [error]);
|
|
54
33
|
}
|
|
55
34
|
}
|
|
56
|
-
|
|
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) {
|
|
57
97
|
errors.forEach(e => {
|
|
58
98
|
this.log((e instanceof OpraException) ? 'error' : 'fatal', incoming, e); // todo. implement a better logger
|
|
59
99
|
});
|
|
@@ -66,20 +106,20 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
66
106
|
return b.status - a.status;
|
|
67
107
|
return i;
|
|
68
108
|
});
|
|
69
|
-
if (!status || status < HttpStatusCodes.BAD_REQUEST) {
|
|
109
|
+
if (!status || status < Number(HttpStatusCodes.BAD_REQUEST)) {
|
|
70
110
|
status = errors[0].status;
|
|
71
|
-
if (status < HttpStatusCodes.BAD_REQUEST)
|
|
111
|
+
if (status < Number(HttpStatusCodes.BAD_REQUEST))
|
|
72
112
|
status = HttpStatusCodes.INTERNAL_SERVER_ERROR;
|
|
73
113
|
}
|
|
74
114
|
const body = this.i18n.deep({
|
|
75
115
|
errors: errors.map(e => e.issue)
|
|
76
116
|
});
|
|
77
|
-
outgoing.
|
|
78
|
-
outgoing.
|
|
79
|
-
outgoing.
|
|
80
|
-
outgoing.
|
|
81
|
-
outgoing.
|
|
82
|
-
outgoing.
|
|
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);
|
|
83
123
|
outgoing.send(JSON.stringify(body));
|
|
84
124
|
outgoing.end();
|
|
85
125
|
}
|
|
@@ -91,402 +131,4 @@ export class OpraHttpAdapter extends OpraAdapter {
|
|
|
91
131
|
return;
|
|
92
132
|
logFn.apply(this.logger, [String(message), ...optionalParams]);
|
|
93
133
|
}
|
|
94
|
-
async executeRequest(context) {
|
|
95
|
-
await super.executeRequest(context);
|
|
96
|
-
const { request } = context;
|
|
97
|
-
const response = context.response;
|
|
98
|
-
const { crud } = request;
|
|
99
|
-
const httpResponse = response.switchToHttp();
|
|
100
|
-
if (request.resource instanceof Singleton || request.resource instanceof Collection) {
|
|
101
|
-
httpResponse.set(HttpHeaderCodes.X_Opra_Data_Type, request.resource.type.name);
|
|
102
|
-
httpResponse.set(HttpHeaderCodes.X_Opra_Operation, request.operation);
|
|
103
|
-
}
|
|
104
|
-
if (crud === 'create') {
|
|
105
|
-
if (!response.value)
|
|
106
|
-
throw new InternalServerError();
|
|
107
|
-
// todo validate
|
|
108
|
-
httpResponse.status(201);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
*
|
|
113
|
-
* @param incoming
|
|
114
|
-
* @protected
|
|
115
|
-
*/
|
|
116
|
-
async parseRequest(incoming) {
|
|
117
|
-
try {
|
|
118
|
-
const url = new OpraURL();
|
|
119
|
-
url.searchParams.define({
|
|
120
|
-
'$search': { codec: 'string' },
|
|
121
|
-
'$pick': { codec: 'string', array: 'strict' },
|
|
122
|
-
'$omit': { codec: 'string', array: 'strict' },
|
|
123
|
-
'$include': { codec: 'string', array: 'strict' },
|
|
124
|
-
'$sort': { codec: 'string', array: 'strict' },
|
|
125
|
-
'$filter': { codec: 'filter' },
|
|
126
|
-
'$limit': { codec: 'number' },
|
|
127
|
-
'$skip': { codec: 'number' },
|
|
128
|
-
'$distinct': { codec: 'boolean' },
|
|
129
|
-
'$count': { codec: 'boolean' },
|
|
130
|
-
});
|
|
131
|
-
url.parse(incoming.url);
|
|
132
|
-
// const {context, url, method, headers, body, contentId} = args;
|
|
133
|
-
if (!url.path.size)
|
|
134
|
-
throw new BadRequestError();
|
|
135
|
-
const method = incoming.method;
|
|
136
|
-
if (method !== 'GET' && url.path.size > 1)
|
|
137
|
-
throw new BadRequestError();
|
|
138
|
-
// const pathLen = url.path.size;
|
|
139
|
-
// let pathIndex = 0;
|
|
140
|
-
const params = url.searchParams;
|
|
141
|
-
const p = url.path.get(0);
|
|
142
|
-
const resource = this._internalDoc.getResource(p.resource);
|
|
143
|
-
// let container: IResourceContainer | undefined;
|
|
144
|
-
// while (resource && resource instanceof ContainerResourceInfo) {
|
|
145
|
-
// container = resource;
|
|
146
|
-
// p = url.path.get(++pathIndex);
|
|
147
|
-
// resource = container.getResource(p.resource);
|
|
148
|
-
// }
|
|
149
|
-
// const headers = incoming.headers;
|
|
150
|
-
/*
|
|
151
|
-
* Collection
|
|
152
|
-
*/
|
|
153
|
-
if (resource instanceof Collection) {
|
|
154
|
-
switch (method) {
|
|
155
|
-
case 'POST': {
|
|
156
|
-
if (!p.key) {
|
|
157
|
-
const pick = params.get('$pick');
|
|
158
|
-
const omit = params.get('$omit');
|
|
159
|
-
const include = params.get('$include');
|
|
160
|
-
return new HttpRequestHost({
|
|
161
|
-
kind: 'CollectionCreateRequest',
|
|
162
|
-
resource,
|
|
163
|
-
operation: 'create',
|
|
164
|
-
crud: 'create',
|
|
165
|
-
many: false,
|
|
166
|
-
args: {
|
|
167
|
-
data: incoming.body,
|
|
168
|
-
pick: pick && resource.normalizeFieldPath(pick),
|
|
169
|
-
omit: omit && resource.normalizeFieldPath(omit),
|
|
170
|
-
include: include && resource.normalizeFieldPath(include)
|
|
171
|
-
}
|
|
172
|
-
}, incoming);
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
case 'DELETE': {
|
|
177
|
-
if (p.key) {
|
|
178
|
-
return new HttpRequestHost({
|
|
179
|
-
kind: 'CollectionDeleteRequest',
|
|
180
|
-
resource,
|
|
181
|
-
operation: 'delete',
|
|
182
|
-
crud: 'delete',
|
|
183
|
-
many: false,
|
|
184
|
-
args: {
|
|
185
|
-
key: resource.parseKeyValue(p.key)
|
|
186
|
-
}
|
|
187
|
-
}, incoming);
|
|
188
|
-
}
|
|
189
|
-
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
190
|
-
return new HttpRequestHost({
|
|
191
|
-
kind: 'CollectionDeleteManyRequest',
|
|
192
|
-
resource,
|
|
193
|
-
operation: 'deleteMany',
|
|
194
|
-
crud: 'delete',
|
|
195
|
-
many: true,
|
|
196
|
-
args: {
|
|
197
|
-
filter
|
|
198
|
-
}
|
|
199
|
-
}, incoming);
|
|
200
|
-
}
|
|
201
|
-
case 'GET': {
|
|
202
|
-
const pick = params.get('$pick');
|
|
203
|
-
const omit = params.get('$omit');
|
|
204
|
-
const include = params.get('$include');
|
|
205
|
-
if (p.key) {
|
|
206
|
-
return new HttpRequestHost({
|
|
207
|
-
kind: 'CollectionGetRequest',
|
|
208
|
-
resource,
|
|
209
|
-
operation: 'get',
|
|
210
|
-
crud: 'read',
|
|
211
|
-
many: false,
|
|
212
|
-
args: {
|
|
213
|
-
key: resource.parseKeyValue(p.key),
|
|
214
|
-
pick: pick && resource.normalizeFieldPath(pick),
|
|
215
|
-
omit: omit && resource.normalizeFieldPath(omit),
|
|
216
|
-
include: include && resource.normalizeFieldPath(include)
|
|
217
|
-
}
|
|
218
|
-
}, incoming);
|
|
219
|
-
}
|
|
220
|
-
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
221
|
-
const sort = params.get('$sort');
|
|
222
|
-
return new HttpRequestHost({
|
|
223
|
-
kind: 'CollectionFindManyRequest',
|
|
224
|
-
resource,
|
|
225
|
-
operation: 'findMany',
|
|
226
|
-
crud: 'read',
|
|
227
|
-
many: true,
|
|
228
|
-
args: {
|
|
229
|
-
pick: pick && resource.normalizeFieldPath(pick),
|
|
230
|
-
omit: omit && resource.normalizeFieldPath(omit),
|
|
231
|
-
include: include && resource.normalizeFieldPath(include),
|
|
232
|
-
sort: sort && resource.normalizeSortFields(sort),
|
|
233
|
-
filter,
|
|
234
|
-
limit: params.get('$limit'),
|
|
235
|
-
skip: params.get('$skip'),
|
|
236
|
-
distinct: params.get('$distinct'),
|
|
237
|
-
count: params.get('$count'),
|
|
238
|
-
}
|
|
239
|
-
}, incoming);
|
|
240
|
-
}
|
|
241
|
-
case 'PATCH': {
|
|
242
|
-
if (p.key) {
|
|
243
|
-
const pick = params.get('$pick');
|
|
244
|
-
const omit = params.get('$omit');
|
|
245
|
-
const include = params.get('$include');
|
|
246
|
-
return new HttpRequestHost({
|
|
247
|
-
kind: 'CollectionUpdateRequest',
|
|
248
|
-
resource,
|
|
249
|
-
operation: 'update',
|
|
250
|
-
crud: 'update',
|
|
251
|
-
many: false,
|
|
252
|
-
args: {
|
|
253
|
-
key: resource.parseKeyValue(p.key),
|
|
254
|
-
data: incoming.body,
|
|
255
|
-
pick: pick && resource.normalizeFieldPath(pick),
|
|
256
|
-
omit: omit && resource.normalizeFieldPath(omit),
|
|
257
|
-
include: include && resource.normalizeFieldPath(include),
|
|
258
|
-
}
|
|
259
|
-
}, incoming);
|
|
260
|
-
}
|
|
261
|
-
const filter = resource.normalizeFilter(params.get('$filter'));
|
|
262
|
-
return new HttpRequestHost({
|
|
263
|
-
kind: 'CollectionUpdateManyRequest',
|
|
264
|
-
resource,
|
|
265
|
-
operation: 'updateMany',
|
|
266
|
-
crud: 'update',
|
|
267
|
-
many: true,
|
|
268
|
-
args: {
|
|
269
|
-
data: incoming.body,
|
|
270
|
-
filter,
|
|
271
|
-
}
|
|
272
|
-
}, incoming);
|
|
273
|
-
}
|
|
274
|
-
default:
|
|
275
|
-
throw new BadRequestError();
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
else
|
|
279
|
-
/*
|
|
280
|
-
* Singleton
|
|
281
|
-
*/
|
|
282
|
-
if (resource instanceof Singleton) {
|
|
283
|
-
if (p.key)
|
|
284
|
-
throw new BadRequestError();
|
|
285
|
-
switch (method) {
|
|
286
|
-
case 'POST': {
|
|
287
|
-
const pick = params.get('$pick');
|
|
288
|
-
const omit = params.get('$omit');
|
|
289
|
-
const include = params.get('$include');
|
|
290
|
-
return new HttpRequestHost({
|
|
291
|
-
kind: 'SingletonCreateRequest',
|
|
292
|
-
resource,
|
|
293
|
-
operation: 'create',
|
|
294
|
-
crud: 'create',
|
|
295
|
-
many: false,
|
|
296
|
-
args: {
|
|
297
|
-
data: incoming.body,
|
|
298
|
-
pick: pick && resource.normalizeFieldPath(pick),
|
|
299
|
-
omit: omit && resource.normalizeFieldPath(omit),
|
|
300
|
-
include: include && resource.normalizeFieldPath(include),
|
|
301
|
-
}
|
|
302
|
-
}, incoming);
|
|
303
|
-
}
|
|
304
|
-
case 'DELETE': {
|
|
305
|
-
return new HttpRequestHost({
|
|
306
|
-
kind: 'SingletonDeleteRequest',
|
|
307
|
-
resource,
|
|
308
|
-
operation: 'delete',
|
|
309
|
-
crud: 'delete',
|
|
310
|
-
many: false,
|
|
311
|
-
args: {}
|
|
312
|
-
}, incoming);
|
|
313
|
-
}
|
|
314
|
-
case 'GET': {
|
|
315
|
-
const pick = params.get('$pick');
|
|
316
|
-
const omit = params.get('$omit');
|
|
317
|
-
const include = params.get('$include');
|
|
318
|
-
return new HttpRequestHost({
|
|
319
|
-
kind: 'SingletonGetRequest',
|
|
320
|
-
resource,
|
|
321
|
-
operation: 'get',
|
|
322
|
-
crud: 'read',
|
|
323
|
-
many: false,
|
|
324
|
-
args: {
|
|
325
|
-
pick: pick && resource.normalizeFieldPath(pick),
|
|
326
|
-
omit: omit && resource.normalizeFieldPath(omit),
|
|
327
|
-
include: include && resource.normalizeFieldPath(include),
|
|
328
|
-
}
|
|
329
|
-
}, incoming);
|
|
330
|
-
}
|
|
331
|
-
case 'PATCH': {
|
|
332
|
-
const pick = params.get('$pick');
|
|
333
|
-
const omit = params.get('$omit');
|
|
334
|
-
const include = params.get('$include');
|
|
335
|
-
return new HttpRequestHost({
|
|
336
|
-
kind: 'SingletonUpdateRequest',
|
|
337
|
-
resource,
|
|
338
|
-
operation: 'update',
|
|
339
|
-
crud: 'update',
|
|
340
|
-
many: false,
|
|
341
|
-
args: {
|
|
342
|
-
data: incoming.body,
|
|
343
|
-
pick: pick && resource.normalizeFieldPath(pick),
|
|
344
|
-
omit: omit && resource.normalizeFieldPath(omit),
|
|
345
|
-
include: include && resource.normalizeFieldPath(include),
|
|
346
|
-
}
|
|
347
|
-
}, incoming);
|
|
348
|
-
}
|
|
349
|
-
default:
|
|
350
|
-
throw new BadRequestError();
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
else
|
|
354
|
-
throw new InternalServerError();
|
|
355
|
-
// if (query instanceof SingletonGetQuery || query instanceof CollectionGetQuery || query instanceof ElementReadQuery) {
|
|
356
|
-
// // Move through properties
|
|
357
|
-
// let parentType: DataType;
|
|
358
|
-
// const curPath: string[] = [];
|
|
359
|
-
// let parent: SingletonGetQuery | CollectionGetQuery | ElementReadQuery = query;
|
|
360
|
-
// while (++pathIndex < pathLen) {
|
|
361
|
-
// p = url.path.get(pathIndex);
|
|
362
|
-
// parentType = parent.type;
|
|
363
|
-
// if (parent.type instanceof UnionType) {
|
|
364
|
-
// if (parent.type.name === 'any')
|
|
365
|
-
// parentType = this.document.getComplexType('object');
|
|
366
|
-
// else
|
|
367
|
-
// throw new TypeError(`"${resource.name}.${curPath.join()}" is a UnionType and needs type casting.`);
|
|
368
|
-
// }
|
|
369
|
-
// if (!(parentType instanceof ComplexType))
|
|
370
|
-
// throw new TypeError(`"${resource.name}.${curPath.join()}" is not a ComplexType and has no fields.`);
|
|
371
|
-
// curPath.push(p.resource);
|
|
372
|
-
// parent.child = new ElementReadQuery(parent, p.resource, {castingType: parentType});
|
|
373
|
-
// parent = parent.child;
|
|
374
|
-
// }
|
|
375
|
-
// }
|
|
376
|
-
throw new MethodNotAllowedError({
|
|
377
|
-
message: `Method "${method}" is not allowed by target endpoint`
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
catch (e) {
|
|
381
|
-
if (e instanceof OpraException)
|
|
382
|
-
throw e;
|
|
383
|
-
throw new BadRequestError(e);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
// async parseMultiPart(
|
|
387
|
-
// context: TExecutionContext,
|
|
388
|
-
// url: OpraURL,
|
|
389
|
-
// headers: IncomingHttpHeaders,
|
|
390
|
-
// input: Readable,
|
|
391
|
-
// boundary: string
|
|
392
|
-
// ): Promise<BatchRequestContext> {
|
|
393
|
-
// return await new Promise((resolve, reject) => {
|
|
394
|
-
// let _resolved = false;
|
|
395
|
-
// const dicer = new Dicer({boundary});
|
|
396
|
-
// const doReject = (e) => {
|
|
397
|
-
// if (_resolved) return;
|
|
398
|
-
// _resolved = true;
|
|
399
|
-
// reject(e);
|
|
400
|
-
// taskQueue.clearQueue();
|
|
401
|
-
// dicer.destroy();
|
|
402
|
-
// }
|
|
403
|
-
// const taskQueue = new TaskQueue({concurrency: 1});
|
|
404
|
-
// taskQueue.on('error', doReject);
|
|
405
|
-
//
|
|
406
|
-
// const queries: SingleRequestContext[] = [];
|
|
407
|
-
// let partCounter = 0;
|
|
408
|
-
// dicer.on('error', doReject);
|
|
409
|
-
// dicer.on('part', part => {
|
|
410
|
-
// const partIndex = partCounter++;
|
|
411
|
-
// let header: any;
|
|
412
|
-
// const chunks: Buffer[] = [];
|
|
413
|
-
// part.on('error', doReject);
|
|
414
|
-
// part.on('header', (_header) => header = normalizeHeaders(_header));
|
|
415
|
-
// part.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
416
|
-
// part.on('end', () => {
|
|
417
|
-
// if (_resolved || !(header || chunks.length))
|
|
418
|
-
// return;
|
|
419
|
-
// const ct = header['content-type'];
|
|
420
|
-
// if (ct === 'application/http') {
|
|
421
|
-
// taskQueue.enqueue(async () => {
|
|
422
|
-
// const data = Buffer.concat(chunks);
|
|
423
|
-
// if (!(data && data.length))
|
|
424
|
-
// return;
|
|
425
|
-
// const r = HttpRequest.parse(data);
|
|
426
|
-
// await callMiddlewares(r, [jsonBodyParser]);
|
|
427
|
-
// const subUrl = new OpraURL(r.url);
|
|
428
|
-
// const contentId = header && header['content-id'];
|
|
429
|
-
// queries.push(this.parseSingleQuery({
|
|
430
|
-
// context,
|
|
431
|
-
// url: subUrl,
|
|
432
|
-
// method: r.method,
|
|
433
|
-
// headers: r.headers,
|
|
434
|
-
// body: r.body,
|
|
435
|
-
// contentId
|
|
436
|
-
// }));
|
|
437
|
-
// });
|
|
438
|
-
// } else doReject(new BadRequestError({
|
|
439
|
-
// message: 'Unaccepted "content-type" header in multipart data',
|
|
440
|
-
// details: {
|
|
441
|
-
// position: `${boundary}[${partIndex}]`
|
|
442
|
-
// }
|
|
443
|
-
// }))
|
|
444
|
-
// });
|
|
445
|
-
// });
|
|
446
|
-
// dicer.on('finish', () => {
|
|
447
|
-
// taskQueue.enqueue(() => {
|
|
448
|
-
// if (_resolved) return;
|
|
449
|
-
// _resolved = true;
|
|
450
|
-
// const batch = new BatchRequestContext({
|
|
451
|
-
// service: this.document,
|
|
452
|
-
// context,
|
|
453
|
-
// headers,
|
|
454
|
-
// queries,
|
|
455
|
-
// params: url.searchParams,
|
|
456
|
-
// continueOnError: false
|
|
457
|
-
// });
|
|
458
|
-
// resolve(batch);
|
|
459
|
-
// });
|
|
460
|
-
// });
|
|
461
|
-
// input.pipe(dicer);
|
|
462
|
-
// });
|
|
463
|
-
// }
|
|
464
|
-
async sendResponse(context) {
|
|
465
|
-
const { request, response } = context;
|
|
466
|
-
const outgoing = response.switchToHttp();
|
|
467
|
-
const errors = response.errors?.map(e => wrapException(e));
|
|
468
|
-
if (errors && errors.length) {
|
|
469
|
-
await this.errorHandler(request.switchToHttp(), outgoing, errors);
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
outgoing.set(HttpHeaderCodes.Cache_Control, 'no-cache');
|
|
473
|
-
outgoing.set(HttpHeaderCodes.Pragma, 'no-cache');
|
|
474
|
-
outgoing.set(HttpHeaderCodes.Expires, '-1');
|
|
475
|
-
outgoing.set(HttpHeaderCodes.X_Opra_Version, OpraSchema.SpecVersion);
|
|
476
|
-
outgoing.status(outgoing.statusCode || HttpStatusCodes.OK);
|
|
477
|
-
if (response.value) {
|
|
478
|
-
if (typeof response.value === 'object') {
|
|
479
|
-
if (isReadable(response.value) || Buffer.isBuffer(response.value))
|
|
480
|
-
outgoing.send(response.value);
|
|
481
|
-
else {
|
|
482
|
-
const body = this.i18n.deep(response.value);
|
|
483
|
-
outgoing.set(HttpHeaderCodes.Content_Type, 'application/json; charset=utf-8');
|
|
484
|
-
outgoing.send(JSON.stringify(body));
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
else
|
|
488
|
-
outgoing.send(JSON.stringify(response.value));
|
|
489
|
-
}
|
|
490
|
-
outgoing.end();
|
|
491
|
-
}
|
|
492
134
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This file contains code blocks from open source NodeJs project
|
|
3
|
+
https://github.com/nodejs/
|
|
4
|
+
*/
|
|
5
|
+
import * as stream from 'stream';
|
|
6
|
+
import { HTTPParser } from '@browsery/http-parser';
|
|
7
|
+
import { isReadable } from '@opra/common';
|
|
8
|
+
import { convertToHeaders, convertToHeadersDistinct } from '../helpers/convert-to-headers.js';
|
|
9
|
+
import { convertToRawHeaders } from '../helpers/convert-to-raw-headers.js';
|
|
10
|
+
export const CRLF = Buffer.from('\r\n');
|
|
11
|
+
export const kHeaders = Symbol('kHeaders');
|
|
12
|
+
export const kHeadersDistinct = Symbol('kHeadersDistinct');
|
|
13
|
+
export const kTrailers = Symbol('kTrailers');
|
|
14
|
+
export const kTrailersDistinct = Symbol('kTrailersDistinct');
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @class HttpIncomingMessageHost
|
|
18
|
+
*/
|
|
19
|
+
export class HttpIncomingMessageHost {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.rawHeaders = [];
|
|
22
|
+
this.rawTrailers = [];
|
|
23
|
+
this.complete = false;
|
|
24
|
+
this.joinDuplicateHeaders = false;
|
|
25
|
+
stream.Readable.apply(this);
|
|
26
|
+
}
|
|
27
|
+
get httpVersion() {
|
|
28
|
+
return this.httpVersionMajor
|
|
29
|
+
? this.httpVersionMajor + '.' + this.httpVersionMinor
|
|
30
|
+
: '';
|
|
31
|
+
}
|
|
32
|
+
get headers() {
|
|
33
|
+
if (!this[kHeaders])
|
|
34
|
+
this[kHeaders] = convertToHeaders(this.rawHeaders, {}, this.joinDuplicateHeaders);
|
|
35
|
+
return this[kHeaders];
|
|
36
|
+
}
|
|
37
|
+
set headers(headers) {
|
|
38
|
+
this[kHeaders] = headers;
|
|
39
|
+
}
|
|
40
|
+
get headersDistinct() {
|
|
41
|
+
if (!this[kHeadersDistinct])
|
|
42
|
+
this[kHeadersDistinct] = convertToHeadersDistinct(this.rawHeaders, {});
|
|
43
|
+
return this[kHeadersDistinct];
|
|
44
|
+
}
|
|
45
|
+
get trailers() {
|
|
46
|
+
if (!this[kTrailers])
|
|
47
|
+
this[kTrailers] = convertToHeaders(this.rawTrailers, {}, this.joinDuplicateHeaders);
|
|
48
|
+
return this[kTrailers];
|
|
49
|
+
}
|
|
50
|
+
set trailers(trailers) {
|
|
51
|
+
this[kTrailers] = trailers;
|
|
52
|
+
}
|
|
53
|
+
get trailersDistinct() {
|
|
54
|
+
if (!this[kTrailersDistinct])
|
|
55
|
+
this[kTrailersDistinct] = convertToHeadersDistinct(this.rawTrailers, {});
|
|
56
|
+
return this[kTrailersDistinct];
|
|
57
|
+
}
|
|
58
|
+
_readConfig(init) {
|
|
59
|
+
this.complete = true;
|
|
60
|
+
this.httpVersionMajor = init?.httpVersionMajor || 1;
|
|
61
|
+
this.httpVersionMinor = init?.httpVersionMinor || 0;
|
|
62
|
+
this.method = (init.method || 'GET').toUpperCase();
|
|
63
|
+
this.url = init.url || '';
|
|
64
|
+
this.body = init.body;
|
|
65
|
+
if (init.headers)
|
|
66
|
+
this.rawHeaders = Array.isArray(init.headers) ? init.headers : convertToRawHeaders(init.headers);
|
|
67
|
+
if (init.trailers)
|
|
68
|
+
this.rawTrailers = Array.isArray(init.trailers) ? init.trailers : convertToRawHeaders(init.trailers);
|
|
69
|
+
this.ip = init.ip || '';
|
|
70
|
+
this.ips = init.ips || (this.ip ? [this.ip] : []);
|
|
71
|
+
}
|
|
72
|
+
_readBuffer(buf) {
|
|
73
|
+
const parser = new HTTPParser(HTTPParser.REQUEST);
|
|
74
|
+
let bodyChunks;
|
|
75
|
+
parser[HTTPParser.kOnHeadersComplete] = (info) => {
|
|
76
|
+
this.httpVersionMajor = info.versionMajor;
|
|
77
|
+
this.httpVersionMinor = info.versionMinor;
|
|
78
|
+
this.rawHeaders = info.headers;
|
|
79
|
+
this.method = HTTPParser.methods[info.method];
|
|
80
|
+
this.url = info.url;
|
|
81
|
+
};
|
|
82
|
+
parser[HTTPParser.kOnHeaders] = (trailers) => {
|
|
83
|
+
this.rawTrailers = trailers;
|
|
84
|
+
};
|
|
85
|
+
parser[HTTPParser.kOnBody] = (chunk, offset, length) => {
|
|
86
|
+
bodyChunks = bodyChunks || [];
|
|
87
|
+
bodyChunks.push(chunk.subarray(offset, offset + length));
|
|
88
|
+
};
|
|
89
|
+
parser[HTTPParser.kOnMessageComplete] = () => {
|
|
90
|
+
this.complete = true;
|
|
91
|
+
if (bodyChunks)
|
|
92
|
+
this.body = Buffer.concat(bodyChunks);
|
|
93
|
+
};
|
|
94
|
+
const buffer = Buffer.from(buf);
|
|
95
|
+
let x = parser.execute(buffer, 0, buffer.length);
|
|
96
|
+
if (typeof x === 'object')
|
|
97
|
+
throw x;
|
|
98
|
+
if (!this.complete) {
|
|
99
|
+
x = parser.execute(CRLF);
|
|
100
|
+
if (typeof x === 'object')
|
|
101
|
+
throw x;
|
|
102
|
+
}
|
|
103
|
+
parser.finish();
|
|
104
|
+
}
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
106
|
+
_readStream(readable) {
|
|
107
|
+
throw new Error('_readStream is not implemented yet');
|
|
108
|
+
}
|
|
109
|
+
static create(init) {
|
|
110
|
+
const msg = new HttpIncomingMessageHost();
|
|
111
|
+
if (Buffer.isBuffer(init))
|
|
112
|
+
msg._readBuffer(init);
|
|
113
|
+
else if (isReadable(init)) {
|
|
114
|
+
throw new Error('fromStream is not implemented yet');
|
|
115
|
+
}
|
|
116
|
+
else if (init)
|
|
117
|
+
msg._readConfig(init);
|
|
118
|
+
return msg;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Apply mixins
|
|
122
|
+
Object.assign(HttpIncomingMessageHost.prototype, stream.Readable.prototype);
|