@3sixty/api-server-fastify 0.20.0-next.0 → 0.20.0-next.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/dist/es/fastifySocketIo.js.map +1 -1
- package/dist/es/fastifyWebServer.js +67 -580
- package/dist/es/fastifyWebServer.js.map +1 -1
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IFastifyWebServerConfig.js.map +1 -1
- package/dist/es/models/IFastifyWebServerConstructorOptions.js.map +1 -1
- package/dist/types/fastifyWebServer.d.ts +31 -16
- package/dist/types/models/IFastifyWebServerConfig.d.ts +5 -2
- package/docs/changelog.md +21 -0
- package/docs/reference/classes/FastifyWebServer.md +671 -40
- package/docs/reference/interfaces/IFastifyWebServerConfig.md +8 -2
- package/locales/.validate-ignore +1 -3
- package/locales/en.json +1 -22
- package/package.json +9 -10
- package/dist/es/models/IRestProcessorChains.js +0 -2
- package/dist/es/models/IRestProcessorChains.js.map +0 -1
- package/dist/es/models/ISocketProcessorChains.js +0 -2
- package/dist/es/models/ISocketProcessorChains.js.map +0 -1
- package/dist/types/models/IRestProcessorChains.d.ts +0 -18
- package/dist/types/models/ISocketProcessorChains.d.ts +0 -26
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Copyright 2024 IOTA Stiftung.
|
|
2
|
+
// Copyright 2026 3Sixty Trade Ltd.
|
|
2
3
|
// SPDX-License-Identifier: Apache-2.0.
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { HeaderTypes, HttpMethod, HttpStatusCode, HeaderHelper, MimeTypes } from "@3sixty/web";
|
|
4
|
+
import { BaseServer } from "@3sixty/api-core-server";
|
|
5
|
+
import { HttpBodyLimit, HttpErrorHelper } from "@3sixty/api-models";
|
|
6
|
+
import { BaseError, GeneralError, Is, StringHelper } from "@3sixty/core";
|
|
7
|
+
import { HttpMethod, HttpStatusCode } from "@3sixty/web";
|
|
8
8
|
import FastifyCompress from "@fastify/compress";
|
|
9
9
|
import FastifyCors from "@fastify/cors";
|
|
10
10
|
import Fastify from "fastify";
|
|
@@ -12,44 +12,11 @@ import FastifySocketIO from "./fastifySocketIo.js";
|
|
|
12
12
|
/**
|
|
13
13
|
* Implementation of the web server using Fastify.
|
|
14
14
|
*/
|
|
15
|
-
export class FastifyWebServer {
|
|
15
|
+
export class FastifyWebServer extends BaseServer {
|
|
16
16
|
/**
|
|
17
17
|
* Runtime name for the class.
|
|
18
18
|
*/
|
|
19
19
|
static CLASS_NAME = "FastifyWebServer";
|
|
20
|
-
/**
|
|
21
|
-
* Default port for running the server.
|
|
22
|
-
* @internal
|
|
23
|
-
*/
|
|
24
|
-
static _DEFAULT_PORT = 3000;
|
|
25
|
-
/**
|
|
26
|
-
* Default host for running the server.
|
|
27
|
-
* @internal
|
|
28
|
-
*/
|
|
29
|
-
static _DEFAULT_HOST = "localhost";
|
|
30
|
-
/**
|
|
31
|
-
* Default named body size limits for routes.
|
|
32
|
-
* @internal
|
|
33
|
-
*/
|
|
34
|
-
static _DEFAULT_BODY_LIMITS = {
|
|
35
|
-
[HttpBodyLimit.Default]: 1048576,
|
|
36
|
-
[HttpBodyLimit.Large]: 26214400
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* The logging component type.
|
|
40
|
-
* @internal
|
|
41
|
-
*/
|
|
42
|
-
_loggingComponentType;
|
|
43
|
-
/**
|
|
44
|
-
* The logging component.
|
|
45
|
-
* @internal
|
|
46
|
-
*/
|
|
47
|
-
_logging;
|
|
48
|
-
/**
|
|
49
|
-
* The options for the server.
|
|
50
|
-
* @internal
|
|
51
|
-
*/
|
|
52
|
-
_options;
|
|
53
20
|
/**
|
|
54
21
|
* The Fastify instance.
|
|
55
22
|
* @internal
|
|
@@ -60,48 +27,16 @@ export class FastifyWebServer {
|
|
|
60
27
|
* @internal
|
|
61
28
|
*/
|
|
62
29
|
_socketConfig;
|
|
63
|
-
/**
|
|
64
|
-
* Whether the server has been started.
|
|
65
|
-
* @internal
|
|
66
|
-
*/
|
|
67
|
-
_started;
|
|
68
|
-
/**
|
|
69
|
-
* The mime type processors.
|
|
70
|
-
* @internal
|
|
71
|
-
*/
|
|
72
|
-
_mimeTypeProcessors;
|
|
73
|
-
/**
|
|
74
|
-
* Include the stack with errors.
|
|
75
|
-
* @internal
|
|
76
|
-
*/
|
|
77
|
-
_includeErrorStack;
|
|
78
|
-
/**
|
|
79
|
-
* The public origin of the server, used for constructing the request URL and for CORS.
|
|
80
|
-
* @internal
|
|
81
|
-
*/
|
|
82
|
-
_publicOrigin;
|
|
83
|
-
/**
|
|
84
|
-
* The local origin of the server, used for constructing the request URL and for CORS.
|
|
85
|
-
* @internal
|
|
86
|
-
*/
|
|
87
|
-
_localOrigin;
|
|
88
|
-
/**
|
|
89
|
-
* The REST processor chains resolved when the server is built.
|
|
90
|
-
* @internal
|
|
91
|
-
*/
|
|
92
|
-
_restChains;
|
|
93
|
-
/**
|
|
94
|
-
* The socket processor chains resolved when the server is built.
|
|
95
|
-
* @internal
|
|
96
|
-
*/
|
|
97
|
-
_socketChains;
|
|
98
30
|
/**
|
|
99
31
|
* Create a new instance of FastifyWebServer.
|
|
100
32
|
* @param options The options for the server.
|
|
101
33
|
*/
|
|
102
34
|
constructor(options) {
|
|
103
|
-
|
|
104
|
-
|
|
35
|
+
super({
|
|
36
|
+
loggingComponentType: options?.loggingComponentType,
|
|
37
|
+
mimeTypeProcessors: options?.mimeTypeProcessors,
|
|
38
|
+
includeErrorStack: options?.config?.includeErrorStack
|
|
39
|
+
});
|
|
105
40
|
this._fastify = Fastify({
|
|
106
41
|
routerOptions: {
|
|
107
42
|
maxParamLength: 2000
|
|
@@ -114,15 +49,6 @@ export class FastifyWebServer {
|
|
|
114
49
|
path: "/socket",
|
|
115
50
|
...options?.config?.socket
|
|
116
51
|
};
|
|
117
|
-
this._started = false;
|
|
118
|
-
this._restChains = { pre: [], process: [], post: [] };
|
|
119
|
-
this._socketChains = { connected: [], disconnected: [], pre: [], process: [], post: [] };
|
|
120
|
-
this._mimeTypeProcessors = options?.mimeTypeProcessors ?? [];
|
|
121
|
-
const hasJsonLd = this._mimeTypeProcessors.some(processor => processor.getTypes().includes(MimeTypes.JsonLd));
|
|
122
|
-
if (!hasJsonLd) {
|
|
123
|
-
this._mimeTypeProcessors.push(new JsonLdMimeTypeProcessor());
|
|
124
|
-
}
|
|
125
|
-
this._includeErrorStack = options?.config?.includeErrorStack ?? false;
|
|
126
52
|
}
|
|
127
53
|
/**
|
|
128
54
|
* Returns the class name of the component.
|
|
@@ -148,36 +74,7 @@ export class FastifyWebServer {
|
|
|
148
74
|
* @returns A promise that resolves when the server is fully built and ready to start.
|
|
149
75
|
*/
|
|
150
76
|
async build(restRouteProcessors, restRoutes, socketRouteProcessors, socketRoutes, options) {
|
|
151
|
-
|
|
152
|
-
throw new GeneralError(FastifyWebServer.CLASS_NAME, "noRestProcessors");
|
|
153
|
-
}
|
|
154
|
-
if (Is.arrayValue(socketRoutes) && !Is.arrayValue(socketRouteProcessors)) {
|
|
155
|
-
throw new GeneralError(FastifyWebServer.CLASS_NAME, "noSocketProcessors");
|
|
156
|
-
}
|
|
157
|
-
await this._logging?.log({
|
|
158
|
-
level: "info",
|
|
159
|
-
ts: Date.now(),
|
|
160
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
161
|
-
message: "building"
|
|
162
|
-
});
|
|
163
|
-
let localHost = options?.host ?? "127.0.0.1";
|
|
164
|
-
if (localHost === "0.0.0.0") {
|
|
165
|
-
localHost = "127.0.0.1";
|
|
166
|
-
}
|
|
167
|
-
this._localOrigin = `http://${localHost}:${options?.port ?? 3000}`;
|
|
168
|
-
if (Is.stringValue(options?.publicOrigin)) {
|
|
169
|
-
const publicUrl = Url.tryParseExact(options.publicOrigin);
|
|
170
|
-
if (!Is.empty(publicUrl)) {
|
|
171
|
-
const urlParts = publicUrl.parts();
|
|
172
|
-
this._publicOrigin = `${urlParts.schema}://${urlParts.host}${Is.integer(urlParts.port) ? `:${urlParts.port}` : ""}`;
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
throw new GeneralError(FastifyWebServer.CLASS_NAME, "invalidPublicOrigin", {
|
|
176
|
-
publicOrigin: options.publicOrigin
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
this._options = options;
|
|
77
|
+
await this.prepareBuild(restRouteProcessors, restRoutes, socketRouteProcessors, socketRoutes, options);
|
|
181
78
|
await this._fastify.register(FastifyCompress);
|
|
182
79
|
if (Is.arrayValue(socketRoutes)) {
|
|
183
80
|
await this._fastify.register(FastifySocketIO, this._socketConfig);
|
|
@@ -197,7 +94,7 @@ export class FastifyWebServer {
|
|
|
197
94
|
}
|
|
198
95
|
}
|
|
199
96
|
await this.initCors(options);
|
|
200
|
-
this._fastify.setNotFoundHandler({}, async (request, reply) => this.handleRequestRest(
|
|
97
|
+
this._fastify.setNotFoundHandler({}, async (request, reply) => this.handleRequestRest(request, reply));
|
|
201
98
|
this._fastify.setErrorHandler(async (error, request, reply) => {
|
|
202
99
|
// If code property is set this is a fastify error
|
|
203
100
|
// otherwise it's from our framework
|
|
@@ -229,182 +126,52 @@ export class FastifyWebServer {
|
|
|
229
126
|
});
|
|
230
127
|
await this.addRoutesRest(restRouteProcessors, restRoutes);
|
|
231
128
|
await this.addRoutesSocket(socketRouteProcessors, socketRoutes);
|
|
232
|
-
this.buildProcessorChains(restRouteProcessors, socketRouteProcessors);
|
|
233
129
|
}
|
|
234
130
|
/**
|
|
235
|
-
* Start the
|
|
236
|
-
* @
|
|
131
|
+
* Start listening for connections on the transport.
|
|
132
|
+
* @param host The host to bind to.
|
|
133
|
+
* @param port The port to bind to.
|
|
134
|
+
* @returns A promise that resolves when the transport is listening.
|
|
237
135
|
*/
|
|
238
|
-
async
|
|
239
|
-
|
|
240
|
-
const port = this._options?.port ?? FastifyWebServer._DEFAULT_PORT;
|
|
241
|
-
await this._logging?.log({
|
|
242
|
-
level: "info",
|
|
243
|
-
ts: Date.now(),
|
|
244
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
245
|
-
message: "starting",
|
|
246
|
-
data: {
|
|
247
|
-
host,
|
|
248
|
-
port
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
if (!this._started) {
|
|
252
|
-
try {
|
|
253
|
-
await this._fastify.listen({ port, host });
|
|
254
|
-
const addresses = this._fastify.addresses();
|
|
255
|
-
const protocol = Is.object(this._fastify.initialConfig.https) ? "https://" : "http://";
|
|
256
|
-
const normalizeAnyHost = (address) => {
|
|
257
|
-
if (address === "0.0.0.0") {
|
|
258
|
-
return "127.0.0.1";
|
|
259
|
-
}
|
|
260
|
-
if (address === "::") {
|
|
261
|
-
return "::1";
|
|
262
|
-
}
|
|
263
|
-
return address;
|
|
264
|
-
};
|
|
265
|
-
const formatAddress = (address, family, forceProtocol) => {
|
|
266
|
-
const displayAddress = normalizeAnyHost(address);
|
|
267
|
-
const isIPv6 = family === "IPv6" || (Is.stringValue(displayAddress) && displayAddress.includes(":"));
|
|
268
|
-
return `${forceProtocol ?? protocol}${isIPv6 ? "[" : ""}${displayAddress}${isIPv6 ? "]" : ""}`;
|
|
269
|
-
};
|
|
270
|
-
const startupAddresses = addresses.map(a => {
|
|
271
|
-
const formatted = formatAddress(a.address, a.family, protocol);
|
|
272
|
-
return `${formatted}:${a.port}`;
|
|
273
|
-
});
|
|
274
|
-
for (const origin of [this._localOrigin, this._publicOrigin]) {
|
|
275
|
-
if (Is.stringValue(origin)) {
|
|
276
|
-
const originUrl = Url.tryParseExact(origin);
|
|
277
|
-
if (!Is.empty(originUrl)) {
|
|
278
|
-
const originParts = originUrl.parts();
|
|
279
|
-
const formatted = formatAddress(originParts.host, undefined, `${originParts.schema}://`);
|
|
280
|
-
startupAddresses.push(`${formatted}${Is.integer(originParts.port) ? `:${originParts.port}` : ""}`);
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
startupAddresses.push(origin);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
const distinctStartupAddresses = [...new Set(startupAddresses)];
|
|
288
|
-
await this._logging?.log({
|
|
289
|
-
level: "info",
|
|
290
|
-
ts: Date.now(),
|
|
291
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
292
|
-
message: "started",
|
|
293
|
-
data: {
|
|
294
|
-
addresses: distinctStartupAddresses.join(", ")
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
this._started = true;
|
|
298
|
-
}
|
|
299
|
-
catch (err) {
|
|
300
|
-
await this._logging?.log({
|
|
301
|
-
level: "error",
|
|
302
|
-
ts: Date.now(),
|
|
303
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
304
|
-
message: "startFailed",
|
|
305
|
-
error: BaseError.fromError(err)
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
}
|
|
136
|
+
async serverListen(host, port) {
|
|
137
|
+
await this._fastify.listen({ port, host });
|
|
309
138
|
}
|
|
310
139
|
/**
|
|
311
|
-
*
|
|
312
|
-
* @returns A promise that resolves when the
|
|
140
|
+
* Close the transport and all of its connections.
|
|
141
|
+
* @returns A promise that resolves when the transport has closed.
|
|
313
142
|
*/
|
|
314
|
-
async
|
|
315
|
-
|
|
316
|
-
this._started = false;
|
|
317
|
-
await this._fastify.close();
|
|
318
|
-
await this._logging?.log({
|
|
319
|
-
level: "info",
|
|
320
|
-
ts: Date.now(),
|
|
321
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
322
|
-
message: "stopped"
|
|
323
|
-
});
|
|
324
|
-
}
|
|
143
|
+
async serverClose() {
|
|
144
|
+
await this._fastify.close();
|
|
325
145
|
}
|
|
326
146
|
/**
|
|
327
|
-
*
|
|
328
|
-
* @returns The
|
|
147
|
+
* Get the addresses the transport is bound to.
|
|
148
|
+
* @returns The bound addresses.
|
|
329
149
|
*/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (this._fastify?.server?.listening) {
|
|
333
|
-
healthCheck = {
|
|
334
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
335
|
-
status: HealthStatus.Ok,
|
|
336
|
-
category: HealthCategory.Connectivity,
|
|
337
|
-
description: "healthConnectivityDescription",
|
|
338
|
-
message: "reachable"
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
else {
|
|
342
|
-
healthCheck = {
|
|
343
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
344
|
-
status: HealthStatus.Error,
|
|
345
|
-
category: HealthCategory.Connectivity,
|
|
346
|
-
description: "healthConnectivityDescription",
|
|
347
|
-
message: "unreachable"
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
return [healthCheck];
|
|
150
|
+
serverAddresses() {
|
|
151
|
+
return this._fastify.addresses();
|
|
351
152
|
}
|
|
352
153
|
/**
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* @param callback The callback to invoke when a deferred health result is ready.
|
|
356
|
-
* @returns The application health status of the component.
|
|
154
|
+
* Whether the transport is serving over TLS.
|
|
155
|
+
* @returns True if the transport is secure.
|
|
357
156
|
*/
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (response.ok && Is.stringValue(body)) {
|
|
377
|
-
return [
|
|
378
|
-
{
|
|
379
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
380
|
-
status: HealthStatus.Ok,
|
|
381
|
-
category: HealthCategory.Application,
|
|
382
|
-
description: "healthApplicationDescription",
|
|
383
|
-
message: "rootEndpointReachable"
|
|
384
|
-
}
|
|
385
|
-
];
|
|
386
|
-
}
|
|
387
|
-
return [
|
|
388
|
-
{
|
|
389
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
390
|
-
status: HealthStatus.Error,
|
|
391
|
-
category: HealthCategory.Application,
|
|
392
|
-
description: "healthApplicationDescription",
|
|
393
|
-
message: "rootEndpointError"
|
|
394
|
-
}
|
|
395
|
-
];
|
|
396
|
-
}
|
|
397
|
-
catch (error) {
|
|
398
|
-
return [
|
|
399
|
-
{
|
|
400
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
401
|
-
status: HealthStatus.Error,
|
|
402
|
-
category: HealthCategory.Application,
|
|
403
|
-
description: "healthApplicationDescription",
|
|
404
|
-
error: BaseError.fromError(error)
|
|
405
|
-
}
|
|
406
|
-
];
|
|
407
|
-
}
|
|
157
|
+
serverIsSecure() {
|
|
158
|
+
// Fastify reports the https option as true rather than the options object
|
|
159
|
+
const https = this._fastify.initialConfig.https;
|
|
160
|
+
return https === true || Is.object(https);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Whether the transport is currently listening for connections.
|
|
164
|
+
* @returns True if the transport is listening.
|
|
165
|
+
*/
|
|
166
|
+
serverIsListening() {
|
|
167
|
+
return this._fastify?.server?.listening ?? false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Whether a GET route is registered for the root path.
|
|
171
|
+
* @returns True if the root route is registered.
|
|
172
|
+
*/
|
|
173
|
+
serverHasRootRoute() {
|
|
174
|
+
return this._fastify.hasRoute({ method: "GET", url: "/" });
|
|
408
175
|
}
|
|
409
176
|
/**
|
|
410
177
|
* Add the REST routes to the server.
|
|
@@ -438,32 +205,10 @@ export class FastifyWebServer {
|
|
|
438
205
|
bodyLimit: bodyLimitKey
|
|
439
206
|
});
|
|
440
207
|
}
|
|
441
|
-
this._fastify[method](path, { bodyLimit }, async (request, reply) => this.handleRequestRest(
|
|
208
|
+
this._fastify[method](path, { bodyLimit }, async (request, reply) => this.handleRequestRest(request, reply, restRoute));
|
|
442
209
|
}
|
|
443
210
|
}
|
|
444
211
|
}
|
|
445
|
-
/**
|
|
446
|
-
* Merge the configured body limits over the built-in ones and validate them.
|
|
447
|
-
* @returns The named body limits in bytes.
|
|
448
|
-
* @throws GeneralError If a limit is not a positive integer.
|
|
449
|
-
* @internal
|
|
450
|
-
*/
|
|
451
|
-
resolveBodyLimits() {
|
|
452
|
-
const bodyLimits = {
|
|
453
|
-
...FastifyWebServer._DEFAULT_BODY_LIMITS,
|
|
454
|
-
...this._options?.bodyLimits
|
|
455
|
-
};
|
|
456
|
-
for (const name of Object.keys(bodyLimits)) {
|
|
457
|
-
const bodyLimit = bodyLimits[name];
|
|
458
|
-
if (!Is.integer(bodyLimit) || bodyLimit <= 0) {
|
|
459
|
-
throw new GeneralError(FastifyWebServer.CLASS_NAME, "invalidBodyLimit", {
|
|
460
|
-
bodyLimit: name,
|
|
461
|
-
value: bodyLimit
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return bodyLimits;
|
|
466
|
-
}
|
|
467
212
|
/**
|
|
468
213
|
* Add the socket routes to the server.
|
|
469
214
|
* @param socketRouteProcessors The processors for the incoming requests.
|
|
@@ -501,10 +246,8 @@ export class FastifyWebServer {
|
|
|
501
246
|
};
|
|
502
247
|
// Pass the connected information on to any processors
|
|
503
248
|
try {
|
|
504
|
-
for (const
|
|
505
|
-
|
|
506
|
-
await socketRouteProcessor.connected(socketServerRequest, socketRoute, this._loggingComponentType);
|
|
507
|
-
}
|
|
249
|
+
for (const connected of this._socketChains.connected) {
|
|
250
|
+
await connected(socketServerRequest, socketRoute, this._loggingComponentType);
|
|
508
251
|
}
|
|
509
252
|
}
|
|
510
253
|
catch (err) {
|
|
@@ -516,10 +259,8 @@ export class FastifyWebServer {
|
|
|
516
259
|
socket.on("disconnect", async () => {
|
|
517
260
|
try {
|
|
518
261
|
// The socket disconnected so notify any processors
|
|
519
|
-
for (const
|
|
520
|
-
|
|
521
|
-
await socketRouteProcessor.disconnected(socketServerRequest, socketRoute, this._loggingComponentType);
|
|
522
|
-
}
|
|
262
|
+
for (const disconnected of this._socketChains.disconnected) {
|
|
263
|
+
await disconnected(socketServerRequest, socketRoute, this._loggingComponentType);
|
|
523
264
|
}
|
|
524
265
|
}
|
|
525
266
|
catch {
|
|
@@ -531,7 +272,7 @@ export class FastifyWebServer {
|
|
|
531
272
|
// The listener is async, so anything escaping it would surface as an unhandled
|
|
532
273
|
// rejection and terminate the process, emit it on the topic instead
|
|
533
274
|
try {
|
|
534
|
-
await this.handleRequestSocket(
|
|
275
|
+
await this.handleRequestSocket(socketRoute, socket, `/${pathParts.join("/")}`, topic, data);
|
|
535
276
|
}
|
|
536
277
|
catch (err) {
|
|
537
278
|
const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
@@ -546,50 +287,27 @@ export class FastifyWebServer {
|
|
|
546
287
|
}
|
|
547
288
|
/**
|
|
548
289
|
* Handle the incoming REST request.
|
|
549
|
-
* @param restRouteProcessors The hooks to process the incoming requests.
|
|
550
290
|
* @param request The incoming request.
|
|
551
291
|
* @param reply The outgoing response.
|
|
552
292
|
* @param restRoute The REST route to handle.
|
|
553
293
|
* @returns The Fastify reply with the response.
|
|
554
294
|
* @internal
|
|
555
295
|
*/
|
|
556
|
-
async handleRequestRest(
|
|
296
|
+
async handleRequestRest(request, reply, restRoute) {
|
|
557
297
|
const port = (request.port === 80 && request.protocol === "http") ||
|
|
558
298
|
(request.port === 443 && request.protocol === "https") ||
|
|
559
299
|
!Is.integer(request.port)
|
|
560
300
|
? ""
|
|
561
301
|
: `:${request.port}`;
|
|
562
302
|
const requestOrigin = `${request.protocol}://${request.hostname}${port}`;
|
|
563
|
-
const httpServerRequest = {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
body: request.body,
|
|
567
|
-
query: request.query,
|
|
568
|
-
pathParams: request.params,
|
|
569
|
-
headers: request.headers
|
|
570
|
-
};
|
|
303
|
+
const httpServerRequest = this.buildServerRequest(request.method.toUpperCase(), `${requestOrigin}${request.url}`, request.body, request.query,
|
|
304
|
+
// The not found handler matches a wildcard, which is not a param of any route
|
|
305
|
+
Is.empty(restRoute) ? {} : request.params, request.headers);
|
|
571
306
|
const httpResponse = {};
|
|
572
|
-
const contextIds =
|
|
573
|
-
[HttpContextIdKeys.IpAddress]: HeaderHelper.extractClientIps(httpServerRequest.headers).join("|"),
|
|
574
|
-
[HttpContextIdKeys.UserAgent]: HeaderHelper.extractUserAgent(httpServerRequest.headers),
|
|
575
|
-
[HttpContextIdKeys.CorrelationId]: HeaderHelper.extractCorrelationId(httpServerRequest.headers),
|
|
576
|
-
[HttpContextIdKeys.RemoteRequest]: RandomHelper.generateUuidV7("compact"),
|
|
577
|
-
[HttpContextIdKeys.LocalOrigin]: this._localOrigin,
|
|
578
|
-
// This can be overridden by a processor if needed, for example a tenant processor
|
|
579
|
-
[HttpContextIdKeys.PublicOrigin]: this._publicOrigin ?? requestOrigin ?? this._localOrigin
|
|
580
|
-
};
|
|
307
|
+
const contextIds = this.buildContextIds(requestOrigin, httpServerRequest.headers);
|
|
581
308
|
const processorState = {};
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
httpServerRequest.pathParams[key] = decodeURIComponent(httpServerRequest.pathParams[key]);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
if (Is.object(httpServerRequest.query)) {
|
|
588
|
-
for (const key of Object.keys(httpServerRequest.query)) {
|
|
589
|
-
httpServerRequest.query[key] = decodeURIComponent(httpServerRequest.query[key]);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
await this.runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, contextIds, processorState);
|
|
309
|
+
await this.runProcessorsRest(restRoute, httpServerRequest, httpResponse, contextIds, processorState);
|
|
310
|
+
this.validateResponse(httpResponse);
|
|
593
311
|
if (!Is.empty(httpResponse.headers)) {
|
|
594
312
|
for (const header of Object.keys(httpResponse.headers)) {
|
|
595
313
|
reply.header(header, httpResponse.headers[header]);
|
|
@@ -597,85 +315,8 @@ export class FastifyWebServer {
|
|
|
597
315
|
}
|
|
598
316
|
return reply.status(httpResponse.statusCode ?? HttpStatusCode.ok).send(httpResponse.body);
|
|
599
317
|
}
|
|
600
|
-
/**
|
|
601
|
-
* Run the REST processors for the route.
|
|
602
|
-
* @param restRouteProcessors The processors to run.
|
|
603
|
-
* @param restRoute The route to process.
|
|
604
|
-
* @param httpServerRequest The incoming request.
|
|
605
|
-
* @param httpResponse The outgoing response.
|
|
606
|
-
* @param contextIds The context IDs of the request.
|
|
607
|
-
* @internal
|
|
608
|
-
*/
|
|
609
|
-
async runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, contextIds, processorState) {
|
|
610
|
-
let hasPreError = false;
|
|
611
|
-
try {
|
|
612
|
-
// Run inside ContextIdStore.run so pre-processors can do tenant-scoped storage lookups.
|
|
613
|
-
await ContextIdStore.run(contextIds, async () => {
|
|
614
|
-
for (const pre of this._restChains.pre) {
|
|
615
|
-
await pre(httpServerRequest, httpResponse, restRoute, contextIds, processorState, {
|
|
616
|
-
loggingComponentType: this._loggingComponentType
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
catch (err) {
|
|
622
|
-
const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
623
|
-
HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode, this._includeErrorStack);
|
|
624
|
-
hasPreError = true;
|
|
625
|
-
}
|
|
626
|
-
// A pre-processor may set an error response without throwing; treat that as halt.
|
|
627
|
-
if (!hasPreError &&
|
|
628
|
-
Is.integer(httpResponse.statusCode) &&
|
|
629
|
-
httpResponse.statusCode >= HttpStatusCode.badRequest) {
|
|
630
|
-
hasPreError = true;
|
|
631
|
-
}
|
|
632
|
-
// Don't run the main processing if there was an error in the pre processing
|
|
633
|
-
// As this is likely to perform tasks such as authentication which may have failed
|
|
634
|
-
if (!hasPreError) {
|
|
635
|
-
try {
|
|
636
|
-
// Run the processors within an async context
|
|
637
|
-
// so that any services can access the context ids
|
|
638
|
-
await ContextIdStore.run(contextIds, async () => {
|
|
639
|
-
for (const process of this._restChains.process) {
|
|
640
|
-
await process(httpServerRequest, httpResponse, restRoute, processorState, {
|
|
641
|
-
loggingComponentType: this._loggingComponentType
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
catch (err) {
|
|
647
|
-
const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
648
|
-
HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode, this._includeErrorStack);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
try {
|
|
652
|
-
// Always run the post processors, even if there was an error earlier
|
|
653
|
-
// as they may perform cleanup tasks, or logging etc
|
|
654
|
-
await ContextIdStore.run(contextIds, async () => {
|
|
655
|
-
for (const post of this._restChains.post) {
|
|
656
|
-
await post(httpServerRequest, httpResponse, restRoute, contextIds, processorState, {
|
|
657
|
-
loggingComponentType: this._loggingComponentType
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
catch (err) {
|
|
663
|
-
// Just log post processor errors
|
|
664
|
-
await this._logging?.log({
|
|
665
|
-
level: "error",
|
|
666
|
-
ts: Date.now(),
|
|
667
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
668
|
-
message: "postProcessorError",
|
|
669
|
-
error: BaseError.fromError(err),
|
|
670
|
-
data: {
|
|
671
|
-
route: restRoute?.path ?? ""
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
318
|
/**
|
|
677
319
|
* Handle the incoming socket request.
|
|
678
|
-
* @param socketRouteProcessors The hooks to process the incoming requests.
|
|
679
320
|
* @param socketRoute The socket route to handle.
|
|
680
321
|
* @param socket The socket to handle.
|
|
681
322
|
* @param fullPath The full path of the socket route.
|
|
@@ -683,7 +324,7 @@ export class FastifyWebServer {
|
|
|
683
324
|
* @param request The incoming request.
|
|
684
325
|
* @internal
|
|
685
326
|
*/
|
|
686
|
-
async handleRequestSocket(
|
|
327
|
+
async handleRequestSocket(socketRoute, socket, fullPath, emitTopic, request) {
|
|
687
328
|
const socketServerRequest = {
|
|
688
329
|
method: HttpMethod.GET,
|
|
689
330
|
url: fullPath,
|
|
@@ -698,180 +339,26 @@ export class FastifyWebServer {
|
|
|
698
339
|
const processorState = {};
|
|
699
340
|
delete socketServerRequest.query?.EIO;
|
|
700
341
|
delete socketServerRequest.query?.transport;
|
|
701
|
-
await this.runProcessorsSocket(
|
|
342
|
+
await this.runProcessorsSocket(socketRoute, socketServerRequest, httpResponse, contextIds, processorState, emitTopic, async (topic, response) => {
|
|
702
343
|
socket.emit(topic, response);
|
|
703
344
|
});
|
|
704
345
|
}
|
|
705
|
-
/**
|
|
706
|
-
* Run the socket processors for the route.
|
|
707
|
-
* @param socketRouteProcessors The processors to run.
|
|
708
|
-
* @param socketRoute The route to process.
|
|
709
|
-
* @param socketServerRequest The incoming request.
|
|
710
|
-
* @param httpResponse The outgoing response.
|
|
711
|
-
* @param contextIds The context IDs of the request.
|
|
712
|
-
* @param processorState The state handed through the processors.
|
|
713
|
-
* @param requestTopic The topic of the request.
|
|
714
|
-
* @param responseEmitter The emitter to send the response on.
|
|
715
|
-
* @internal
|
|
716
|
-
*/
|
|
717
|
-
async runProcessorsSocket(socketRouteProcessors, socketRoute, socketServerRequest, httpResponse, contextIds, processorState, requestTopic, responseEmitter) {
|
|
718
|
-
// Custom emit method which will also call the post processors
|
|
719
|
-
const postProcessEmit = async (topic, response, responseProcessorState) => {
|
|
720
|
-
await responseEmitter(topic, response);
|
|
721
|
-
try {
|
|
722
|
-
// The post processors are called after the response has been emitted
|
|
723
|
-
for (const post of this._socketChains.post) {
|
|
724
|
-
await post(socketServerRequest, response, socketRoute, contextIds, responseProcessorState, {
|
|
725
|
-
loggingComponentType: this._loggingComponentType
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
catch (err) {
|
|
730
|
-
await this._logging?.log({
|
|
731
|
-
level: "error",
|
|
732
|
-
ts: Date.now(),
|
|
733
|
-
source: FastifyWebServer.CLASS_NAME,
|
|
734
|
-
message: "postProcessorError",
|
|
735
|
-
error: BaseError.fromError(err),
|
|
736
|
-
data: {
|
|
737
|
-
route: socketRoute.path
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
};
|
|
742
|
-
try {
|
|
743
|
-
for (const pre of this._socketChains.pre) {
|
|
744
|
-
await pre(socketServerRequest, httpResponse, socketRoute, contextIds, processorState, {
|
|
745
|
-
loggingComponentType: this._loggingComponentType
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
// We always call all the processors regardless of any response set by a previous processor.
|
|
749
|
-
// But if a pre processor sets a status code, we will emit the response manually, as the pre
|
|
750
|
-
// and post processors do not receive the emit method, they just populate the response object.
|
|
751
|
-
if (!Is.empty(httpResponse.statusCode)) {
|
|
752
|
-
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
753
|
-
}
|
|
754
|
-
await ContextIdStore.run(contextIds, async () => {
|
|
755
|
-
for (const process of this._socketChains.process) {
|
|
756
|
-
await process(socketServerRequest, httpResponse, socketRoute, processorState, async (topic, processResponse) => {
|
|
757
|
-
await postProcessEmit(topic, processResponse, processorState);
|
|
758
|
-
}, this._loggingComponentType);
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
// If the processors set the status to any kind of error then we should emit this manually
|
|
762
|
-
if (Is.integer(httpResponse.statusCode) &&
|
|
763
|
-
httpResponse.statusCode >= HttpStatusCode.badRequest) {
|
|
764
|
-
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
catch (err) {
|
|
768
|
-
// Emit any unhandled errors manually
|
|
769
|
-
const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
770
|
-
HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode, this._includeErrorStack);
|
|
771
|
-
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
346
|
/**
|
|
775
347
|
* Initialize the cors options.
|
|
776
348
|
* @param options The web server options.
|
|
777
349
|
* @internal
|
|
778
350
|
*/
|
|
779
351
|
async initCors(options) {
|
|
780
|
-
|
|
781
|
-
if (Is.arrayValue(options?.corsOrigins)) {
|
|
782
|
-
origins = options?.corsOrigins;
|
|
783
|
-
}
|
|
784
|
-
else if (Is.stringValue(options?.corsOrigins)) {
|
|
785
|
-
origins = [options?.corsOrigins];
|
|
786
|
-
}
|
|
787
|
-
const hasWildcardOrigin = origins.includes("*");
|
|
788
|
-
const methods = options?.methods ?? [
|
|
789
|
-
HttpMethod.GET,
|
|
790
|
-
HttpMethod.PUT,
|
|
791
|
-
HttpMethod.POST,
|
|
792
|
-
HttpMethod.PATCH,
|
|
793
|
-
HttpMethod.DELETE,
|
|
794
|
-
HttpMethod.OPTIONS
|
|
795
|
-
];
|
|
796
|
-
const allowedHeaders = [
|
|
797
|
-
"Access-Control-Allow-Origin",
|
|
798
|
-
"Content-Encoding",
|
|
799
|
-
"Accept-Encoding",
|
|
800
|
-
HeaderTypes.ContentType,
|
|
801
|
-
HeaderTypes.Authorization,
|
|
802
|
-
HeaderTypes.Accept
|
|
803
|
-
];
|
|
804
|
-
const exposedHeaders = [HeaderTypes.ContentDisposition, HeaderTypes.Location];
|
|
805
|
-
if (Is.arrayValue(options?.allowedHeaders)) {
|
|
806
|
-
allowedHeaders.push(...options.allowedHeaders);
|
|
807
|
-
}
|
|
808
|
-
if (Is.arrayValue(options?.exposedHeaders)) {
|
|
809
|
-
exposedHeaders.push(...options.exposedHeaders);
|
|
810
|
-
}
|
|
352
|
+
const corsOptions = this.resolveCorsOptions(options);
|
|
811
353
|
await this._fastify.register(FastifyCors, {
|
|
812
354
|
origin: (origin, callback) => {
|
|
813
|
-
callback(null, hasWildcardOrigin ? true : origins.includes(origin));
|
|
355
|
+
callback(null, corsOptions.hasWildcardOrigin ? true : corsOptions.origins.includes(origin));
|
|
814
356
|
},
|
|
815
|
-
methods,
|
|
816
|
-
allowedHeaders,
|
|
817
|
-
exposedHeaders,
|
|
357
|
+
methods: corsOptions.methods,
|
|
358
|
+
allowedHeaders: corsOptions.allowedHeaders,
|
|
359
|
+
exposedHeaders: corsOptions.exposedHeaders,
|
|
818
360
|
credentials: true
|
|
819
361
|
});
|
|
820
362
|
}
|
|
821
|
-
/**
|
|
822
|
-
* Resolve the route processors in to per phase chains, so that the lookups and bindings
|
|
823
|
-
* are performed once when the server is built instead of on every request.
|
|
824
|
-
* @param restRouteProcessors The processors for the incoming REST requests.
|
|
825
|
-
* @param socketRouteProcessors The processors for the incoming socket requests.
|
|
826
|
-
* @internal
|
|
827
|
-
*/
|
|
828
|
-
buildProcessorChains(restRouteProcessors, socketRouteProcessors) {
|
|
829
|
-
const restChains = { pre: [], process: [], post: [] };
|
|
830
|
-
for (const routeProcessor of restRouteProcessors ?? []) {
|
|
831
|
-
const pre = routeProcessor.pre?.bind(routeProcessor);
|
|
832
|
-
if (Is.function(pre)) {
|
|
833
|
-
restChains.pre.push(pre);
|
|
834
|
-
}
|
|
835
|
-
const process = routeProcessor.process?.bind(routeProcessor);
|
|
836
|
-
if (Is.function(process)) {
|
|
837
|
-
restChains.process.push(process);
|
|
838
|
-
}
|
|
839
|
-
const post = routeProcessor.post?.bind(routeProcessor);
|
|
840
|
-
if (Is.function(post)) {
|
|
841
|
-
restChains.post.push(post);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
const socketChains = {
|
|
845
|
-
connected: [],
|
|
846
|
-
disconnected: [],
|
|
847
|
-
pre: [],
|
|
848
|
-
process: [],
|
|
849
|
-
post: []
|
|
850
|
-
};
|
|
851
|
-
for (const routeProcessor of socketRouteProcessors ?? []) {
|
|
852
|
-
const connected = routeProcessor.connected?.bind(routeProcessor);
|
|
853
|
-
if (Is.function(connected)) {
|
|
854
|
-
socketChains.connected.push(connected);
|
|
855
|
-
}
|
|
856
|
-
const disconnected = routeProcessor.disconnected?.bind(routeProcessor);
|
|
857
|
-
if (Is.function(disconnected)) {
|
|
858
|
-
socketChains.disconnected.push(disconnected);
|
|
859
|
-
}
|
|
860
|
-
const pre = routeProcessor.pre?.bind(routeProcessor);
|
|
861
|
-
if (Is.function(pre)) {
|
|
862
|
-
socketChains.pre.push(pre);
|
|
863
|
-
}
|
|
864
|
-
const process = routeProcessor.process?.bind(routeProcessor);
|
|
865
|
-
if (Is.function(process)) {
|
|
866
|
-
socketChains.process.push(process);
|
|
867
|
-
}
|
|
868
|
-
const post = routeProcessor.post?.bind(routeProcessor);
|
|
869
|
-
if (Is.function(post)) {
|
|
870
|
-
socketChains.post.push(post);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
this._restChains = restChains;
|
|
874
|
-
this._socketChains = socketChains;
|
|
875
|
-
}
|
|
876
363
|
}
|
|
877
364
|
//# sourceMappingURL=fastifyWebServer.js.map
|