kuzzle 2.19.2 → 2.19.3
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/lib/api/controllers/adminController.js +94 -80
- package/lib/api/controllers/authController.js +239 -212
- package/lib/api/controllers/baseController.js +89 -51
- package/lib/api/controllers/bulkController.js +62 -49
- package/lib/api/controllers/clusterController.js +6 -8
- package/lib/api/controllers/collectionController.js +140 -129
- package/lib/api/controllers/debugController.d.ts +2 -2
- package/lib/api/controllers/debugController.js +33 -31
- package/lib/api/controllers/documentController.js +365 -274
- package/lib/api/controllers/index.js +13 -13
- package/lib/api/controllers/indexController.js +46 -50
- package/lib/api/controllers/memoryStorageController.js +410 -360
- package/lib/api/controllers/realtimeController.js +37 -36
- package/lib/api/controllers/securityController.js +553 -412
- package/lib/api/controllers/serverController.js +111 -104
- package/lib/api/documentExtractor.js +75 -68
- package/lib/api/funnel.js +411 -312
- package/lib/api/httpRoutes.js +1493 -324
- package/lib/api/openapi/OpenApiManager.d.ts +1 -1
- package/lib/api/openapi/OpenApiManager.js +22 -22
- package/lib/api/openapi/components/document/count.yaml +1 -1
- package/lib/api/openapi/components/document/create.yaml +2 -2
- package/lib/api/openapi/components/document/delete.yaml +1 -1
- package/lib/api/openapi/components/document/deleteByQuery.yaml +1 -1
- package/lib/api/openapi/components/document/exists.yaml +1 -1
- package/lib/api/openapi/components/document/get.yaml +2 -2
- package/lib/api/openapi/components/document/index.js +12 -12
- package/lib/api/openapi/components/document/replace.yaml +1 -1
- package/lib/api/openapi/components/document/scroll.yaml +1 -1
- package/lib/api/openapi/components/document/validate.yaml +1 -1
- package/lib/api/openapi/components/index.d.ts +2 -2
- package/lib/api/openapi/components/index.js +1 -1
- package/lib/api/openapi/components/security/index.js +1 -1
- package/lib/api/openapi/components/security/upsertUser.yaml +2 -3
- package/lib/api/openapi/index.d.ts +1 -1
- package/lib/api/openapi/openApiGenerator.d.ts +1 -1
- package/lib/api/openapi/openApiGenerator.js +7 -7
- package/lib/api/rateLimiter.js +12 -13
- package/lib/api/request/index.d.ts +4 -4
- package/lib/api/request/kuzzleRequest.d.ts +9 -9
- package/lib/api/request/kuzzleRequest.js +89 -87
- package/lib/api/request/requestContext.d.ts +2 -2
- package/lib/api/request/requestContext.js +17 -17
- package/lib/api/request/requestInput.d.ts +1 -1
- package/lib/api/request/requestInput.js +19 -19
- package/lib/api/request/requestResponse.d.ts +4 -4
- package/lib/api/request/requestResponse.js +31 -33
- package/lib/cluster/command.js +48 -44
- package/lib/cluster/idCardHandler.d.ts +1 -1
- package/lib/cluster/idCardHandler.js +15 -15
- package/lib/cluster/index.js +2 -2
- package/lib/cluster/node.js +301 -269
- package/lib/cluster/publisher.js +45 -46
- package/lib/cluster/state.d.ts +5 -5
- package/lib/cluster/state.js +8 -8
- package/lib/cluster/subscriber.js +163 -113
- package/lib/cluster/workers/IDCardRenewer.js +33 -32
- package/lib/config/default.config.d.ts +1 -1
- package/lib/config/default.config.js +212 -171
- package/lib/config/documentEventAliases.js +6 -6
- package/lib/config/index.js +161 -98
- package/lib/config/sdkCompatibility.json +8 -8
- package/lib/core/auth/formatProcessing.js +7 -7
- package/lib/core/auth/passportResponse.js +7 -7
- package/lib/core/auth/passportWrapper.js +34 -30
- package/lib/core/auth/tokenManager.d.ts +2 -2
- package/lib/core/auth/tokenManager.js +11 -10
- package/lib/core/backend/applicationManager.d.ts +1 -1
- package/lib/core/backend/applicationManager.js +2 -2
- package/lib/core/backend/backend.d.ts +3 -3
- package/lib/core/backend/backend.js +34 -31
- package/lib/core/backend/backendCluster.d.ts +2 -2
- package/lib/core/backend/backendCluster.js +5 -5
- package/lib/core/backend/backendConfig.d.ts +2 -2
- package/lib/core/backend/backendConfig.js +3 -3
- package/lib/core/backend/backendController.d.ts +2 -2
- package/lib/core/backend/backendController.js +9 -10
- package/lib/core/backend/backendErrors.d.ts +3 -3
- package/lib/core/backend/backendErrors.js +2 -1
- package/lib/core/backend/backendHook.d.ts +2 -2
- package/lib/core/backend/backendHook.js +5 -5
- package/lib/core/backend/backendImport.d.ts +3 -3
- package/lib/core/backend/backendImport.js +23 -23
- package/lib/core/backend/backendOpenApi.d.ts +2 -2
- package/lib/core/backend/backendOpenApi.js +16 -16
- package/lib/core/backend/backendPipe.d.ts +2 -2
- package/lib/core/backend/backendPipe.js +6 -6
- package/lib/core/backend/backendPlugin.d.ts +4 -4
- package/lib/core/backend/backendPlugin.js +14 -14
- package/lib/core/backend/backendStorage.d.ts +2 -2
- package/lib/core/backend/backendStorage.js +1 -2
- package/lib/core/backend/backendVault.d.ts +2 -2
- package/lib/core/backend/backendVault.js +3 -3
- package/lib/core/backend/index.d.ts +14 -14
- package/lib/core/backend/internalLogger.d.ts +1 -1
- package/lib/core/backend/internalLogger.js +5 -5
- package/lib/core/cache/cacheDbEnum.js +4 -4
- package/lib/core/cache/cacheEngine.js +79 -85
- package/lib/core/network/accessLogger.js +126 -120
- package/lib/core/network/clientConnection.js +5 -5
- package/lib/core/network/context.js +8 -8
- package/lib/core/network/entryPoint.js +100 -85
- package/lib/core/network/httpRouter/index.js +63 -60
- package/lib/core/network/httpRouter/routeHandler.js +18 -19
- package/lib/core/network/httpRouter/routePart.js +23 -19
- package/lib/core/network/protocolManifest.js +3 -3
- package/lib/core/network/protocols/httpMessage.js +8 -10
- package/lib/core/network/protocols/httpwsProtocol.js +305 -250
- package/lib/core/network/protocols/internalProtocol.js +27 -24
- package/lib/core/network/protocols/mqttProtocol.js +106 -96
- package/lib/core/network/protocols/protocol.js +20 -17
- package/lib/core/network/router.js +56 -46
- package/lib/core/plugin/plugin.js +151 -120
- package/lib/core/plugin/pluginContext.d.ts +7 -7
- package/lib/core/plugin/pluginContext.js +48 -44
- package/lib/core/plugin/pluginManifest.js +13 -12
- package/lib/core/plugin/pluginRepository.js +26 -27
- package/lib/core/plugin/pluginsManager.js +425 -304
- package/lib/core/plugin/privilegedContext.js +3 -3
- package/lib/core/realtime/actionEnum.js +1 -1
- package/lib/core/realtime/channel.d.ts +1 -1
- package/lib/core/realtime/channel.js +22 -22
- package/lib/core/realtime/connectionRooms.d.ts +1 -1
- package/lib/core/realtime/hotelClerk.d.ts +2 -2
- package/lib/core/realtime/hotelClerk.js +53 -50
- package/lib/core/realtime/index.js +5 -5
- package/lib/core/realtime/notification/document.js +25 -25
- package/lib/core/realtime/notification/index.js +4 -4
- package/lib/core/realtime/notification/server.js +3 -3
- package/lib/core/realtime/notification/user.js +4 -4
- package/lib/core/realtime/notifier.js +113 -75
- package/lib/core/realtime/room.d.ts +1 -1
- package/lib/core/realtime/subscription.d.ts +1 -1
- package/lib/core/realtime/subscription.js +1 -1
- package/lib/core/security/index.js +8 -8
- package/lib/core/security/profileRepository.d.ts +6 -6
- package/lib/core/security/profileRepository.js +48 -45
- package/lib/core/security/roleRepository.js +127 -115
- package/lib/core/security/securityLoader.js +70 -63
- package/lib/core/security/tokenRepository.js +132 -118
- package/lib/core/security/userRepository.js +104 -88
- package/lib/core/shared/KoncordeWrapper.d.ts +1 -1
- package/lib/core/shared/KoncordeWrapper.js +3 -1
- package/lib/core/shared/abstractManifest.js +22 -23
- package/lib/core/shared/repository.js +69 -67
- package/lib/core/shared/sdk/embeddedSdk.d.ts +2 -2
- package/lib/core/shared/sdk/embeddedSdk.js +36 -32
- package/lib/core/shared/sdk/funnelProtocol.d.ts +1 -1
- package/lib/core/shared/sdk/funnelProtocol.js +11 -11
- package/lib/core/shared/sdk/impersonatedSdk.js +19 -18
- package/lib/core/shared/store.js +127 -32
- package/lib/core/statistics/index.js +2 -2
- package/lib/core/statistics/statistics.js +99 -85
- package/lib/core/storage/clientAdapter.js +219 -136
- package/lib/core/storage/indexCache.js +3 -3
- package/lib/core/storage/storageEngine.js +10 -13
- package/lib/core/storage/storeScopeEnum.js +3 -3
- package/lib/core/validation/baseType.js +12 -10
- package/lib/core/validation/index.js +2 -2
- package/lib/core/validation/types/anything.js +4 -4
- package/lib/core/validation/types/boolean.js +7 -7
- package/lib/core/validation/types/date.js +165 -131
- package/lib/core/validation/types/email.js +18 -21
- package/lib/core/validation/types/enum.js +34 -21
- package/lib/core/validation/types/geoPoint.js +7 -7
- package/lib/core/validation/types/geoShape.js +148 -125
- package/lib/core/validation/types/integer.js +9 -9
- package/lib/core/validation/types/ipAddress.js +17 -19
- package/lib/core/validation/types/numeric.js +36 -29
- package/lib/core/validation/types/object.js +19 -19
- package/lib/core/validation/types/string.js +36 -29
- package/lib/core/validation/types/url.js +17 -19
- package/lib/core/validation/validation.js +422 -378
- package/lib/kerror/codes/1-services.json +7 -1
- package/lib/kerror/codes/4-plugin.json +2 -2
- package/lib/kerror/codes/index.js +85 -63
- package/lib/kerror/errors/badRequestError.d.ts +1 -1
- package/lib/kerror/errors/externalServiceError.d.ts +1 -1
- package/lib/kerror/errors/forbiddenError.d.ts +1 -1
- package/lib/kerror/errors/gatewayTimeoutError.d.ts +1 -1
- package/lib/kerror/errors/index.d.ts +15 -15
- package/lib/kerror/errors/internalError.d.ts +1 -1
- package/lib/kerror/errors/kuzzleError.d.ts +1 -1
- package/lib/kerror/errors/multipleErrorsError.d.ts +1 -1
- package/lib/kerror/errors/multipleErrorsError.js +1 -1
- package/lib/kerror/errors/notFoundError.d.ts +1 -1
- package/lib/kerror/errors/partialError.d.ts +1 -1
- package/lib/kerror/errors/partialError.js +1 -1
- package/lib/kerror/errors/pluginImplementationError.d.ts +1 -1
- package/lib/kerror/errors/pluginImplementationError.js +2 -1
- package/lib/kerror/errors/preconditionError.d.ts +1 -1
- package/lib/kerror/errors/serviceUnavailableError.d.ts +1 -1
- package/lib/kerror/errors/sizeLimitError.d.ts +1 -1
- package/lib/kerror/errors/tooManyRequestsError.d.ts +1 -1
- package/lib/kerror/errors/unauthorizedError.d.ts +1 -1
- package/lib/kerror/index.d.ts +3 -3
- package/lib/kerror/index.js +17 -16
- package/lib/kuzzle/dumpGenerator.js +130 -114
- package/lib/kuzzle/event/kuzzleEventEmitter.js +96 -70
- package/lib/kuzzle/event/pipeRunner.js +25 -24
- package/lib/kuzzle/event/waterfall.js +13 -15
- package/lib/kuzzle/index.js +2 -2
- package/lib/kuzzle/internalIndexHandler.js +80 -59
- package/lib/kuzzle/kuzzle.js +99 -99
- package/lib/kuzzle/kuzzleStateEnum.js +1 -1
- package/lib/kuzzle/log.js +23 -18
- package/lib/kuzzle/vault.js +34 -19
- package/lib/model/security/profile.d.ts +3 -3
- package/lib/model/security/profile.js +38 -37
- package/lib/model/security/rights.js +5 -5
- package/lib/model/security/role.d.ts +3 -3
- package/lib/model/security/role.js +25 -26
- package/lib/model/security/token.d.ts +1 -1
- package/lib/model/security/token.js +4 -4
- package/lib/model/security/user.d.ts +2 -2
- package/lib/model/security/user.js +9 -9
- package/lib/model/storage/apiKey.js +43 -33
- package/lib/model/storage/baseModel.js +49 -45
- package/lib/service/cache/redis.js +60 -55
- package/lib/service/service.js +17 -17
- package/lib/service/storage/elasticsearch.js +839 -755
- package/lib/service/storage/esWrapper.js +103 -86
- package/lib/service/storage/queryTranslator.js +52 -59
- package/lib/types/Controller.d.ts +3 -3
- package/lib/types/ControllerDefinition.d.ts +3 -3
- package/lib/types/DebugModule.d.ts +2 -2
- package/lib/types/DebugModule.js +1 -1
- package/lib/types/Global.d.ts +1 -1
- package/lib/types/HttpStream.d.ts +2 -1
- package/lib/types/HttpStream.js +7 -5
- package/lib/types/Kuzzle.d.ts +1 -1
- package/lib/types/KuzzleDocument.d.ts +1 -1
- package/lib/types/OpenApiDefinition.d.ts +1 -1
- package/lib/types/PasswordPolicy.d.ts +1 -1
- package/lib/types/Plugin.d.ts +6 -6
- package/lib/types/Plugin.js +2 -2
- package/lib/types/Policy.d.ts +1 -1
- package/lib/types/RequestPayload.d.ts +1 -1
- package/lib/types/ResponsePayload.d.ts +1 -1
- package/lib/types/Token.d.ts +1 -1
- package/lib/types/User.d.ts +1 -1
- package/lib/types/config/DumpConfiguration.d.ts +8 -8
- package/lib/types/config/HttpConfiguration.d.ts +1 -1
- package/lib/types/config/KuzzleConfiguration.d.ts +1 -1
- package/lib/types/config/LimitsConfiguration.d.ts +8 -8
- package/lib/types/config/PluginsConfiguration.d.ts +4 -4
- package/lib/types/config/SecurityConfiguration.d.ts +62 -62
- package/lib/types/config/ServerConfiguration.d.ts +55 -55
- package/lib/types/config/ServicesConfiguration.d.ts +2 -2
- package/lib/types/config/internalCache/InternalCacheRedisConfiguration.d.ts +10 -10
- package/lib/types/config/publicCache/PublicCacheRedisConfiguration.d.ts +3 -3
- package/lib/types/config/storageEngine/StorageEngineElasticsearchConfiguration.d.ts +194 -110
- package/lib/types/errors/ErrorDefinition.d.ts +1 -1
- package/lib/types/errors/ErrorDomains.d.ts +1 -1
- package/lib/types/index.d.ts +38 -38
- package/lib/types/realtime/RealtimeScope.d.ts +1 -1
- package/lib/types/realtime/RealtimeUsers.d.ts +1 -1
- package/lib/util/assertType.js +13 -11
- package/lib/util/async.d.ts +1 -0
- package/lib/util/async.js +61 -0
- package/lib/util/asyncStore.js +19 -21
- package/lib/util/bufferedPassThrough.d.ts +2 -2
- package/lib/util/bufferedPassThrough.js +4 -4
- package/lib/util/bytes.js +9 -13
- package/lib/util/crypto.js +1 -1
- package/lib/util/debug.js +5 -5
- package/lib/util/deprecate.js +24 -21
- package/lib/util/didYouMean.js +7 -7
- package/lib/util/dump-collection.d.ts +2 -2
- package/lib/util/dump-collection.js +26 -26
- package/lib/util/esRequest.d.ts +1 -0
- package/lib/util/esRequest.js +62 -0
- package/lib/util/extractFields.js +24 -25
- package/lib/util/inflector.js +5 -5
- package/lib/util/koncordeCompat.d.ts +2 -2
- package/lib/util/koncordeCompat.js +5 -5
- package/lib/util/memoize.js +3 -5
- package/lib/util/mutex.d.ts +19 -1
- package/lib/util/mutex.js +39 -12
- package/lib/util/name-generator.js +1331 -1331
- package/lib/util/promback.js +8 -10
- package/lib/util/readYamlFile.d.ts +1 -1
- package/lib/util/readYamlFile.js +1 -1
- package/lib/util/requestAssertions.js +34 -34
- package/lib/util/safeObject.js +5 -5
- package/lib/util/stackTrace.js +20 -22
- package/lib/util/wildcard.js +15 -15
- package/package.json +6 -6
- package/npm-shrinkwrap.json +0 -19422
|
@@ -19,28 +19,28 @@
|
|
|
19
19
|
* limitations under the License.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
"use strict";
|
|
23
23
|
|
|
24
|
-
const querystring = require(
|
|
25
|
-
const url = require(
|
|
26
|
-
const zlib = require(
|
|
24
|
+
const querystring = require("querystring");
|
|
25
|
+
const url = require("url");
|
|
26
|
+
const zlib = require("zlib");
|
|
27
27
|
|
|
28
|
-
const uWS = require(
|
|
28
|
+
const uWS = require("uWebSockets.js");
|
|
29
29
|
|
|
30
|
-
const { HttpStream } = require(
|
|
31
|
-
const { Request } = require(
|
|
32
|
-
const { KuzzleError } = require(
|
|
33
|
-
const Protocol = require(
|
|
34
|
-
const ClientConnection = require(
|
|
35
|
-
const { removeStacktrace } = require(
|
|
36
|
-
const debug = require(
|
|
37
|
-
const kerror = require(
|
|
38
|
-
const HttpMessage = require(
|
|
30
|
+
const { HttpStream } = require("../../../types");
|
|
31
|
+
const { Request } = require("../../../api/request");
|
|
32
|
+
const { KuzzleError } = require("../../../kerror/errors");
|
|
33
|
+
const Protocol = require("./protocol");
|
|
34
|
+
const ClientConnection = require("../clientConnection");
|
|
35
|
+
const { removeStacktrace } = require("../../../util/stackTrace");
|
|
36
|
+
const debug = require("../../../util/debug");
|
|
37
|
+
const kerror = require("../../../kerror");
|
|
38
|
+
const HttpMessage = require("./httpMessage");
|
|
39
39
|
|
|
40
|
-
const kerrorWS = kerror.wrap(
|
|
41
|
-
const kerrorHTTP = kerror.wrap(
|
|
42
|
-
const debugWS = debug(
|
|
43
|
-
const debugHTTP = debug(
|
|
40
|
+
const kerrorWS = kerror.wrap("network", "websocket");
|
|
41
|
+
const kerrorHTTP = kerror.wrap("network", "http");
|
|
42
|
+
const debugWS = debug("kuzzle:network:protocols:websocket");
|
|
43
|
+
const debugHTTP = debug("kuzzle:network:protocols:http");
|
|
44
44
|
|
|
45
45
|
// The idleTimeout option should never be deactivated, so instead we use
|
|
46
46
|
// a default value for backward-compatibility
|
|
@@ -65,31 +65,35 @@ const JSON_ENDER = '"}';
|
|
|
65
65
|
|
|
66
66
|
// Pre-computed messages & errors
|
|
67
67
|
const WS_FORCED_TERMINATION_CODE = 1011;
|
|
68
|
-
const WS_BACKPRESSURE_MESSAGE = Buffer.from(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
68
|
+
const WS_BACKPRESSURE_MESSAGE = Buffer.from(
|
|
69
|
+
"too much backpressure: client is too slow"
|
|
70
|
+
);
|
|
71
|
+
const WS_GENERIC_CLOSE_MESSAGE = Buffer.from(
|
|
72
|
+
"Connection closed by remote host"
|
|
73
|
+
);
|
|
74
|
+
const WS_RATE_LIMIT_EXCEEDED_ERROR = kerrorWS.get("ratelimit_exceeded");
|
|
75
|
+
const HTTP_REQUEST_TOO_LARGE_ERROR = kerrorHTTP.get("request_too_large");
|
|
76
|
+
const HTTP_FILE_TOO_LARGE_ERROR = kerrorHTTP.get("file_too_large");
|
|
73
77
|
|
|
74
78
|
// HTTP-related constants
|
|
75
79
|
const HTTP_ALLOWED_CONTENT_TYPES = [
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
"application/json",
|
|
81
|
+
"application/x-www-form-urlencoded",
|
|
82
|
+
"multipart/form-data",
|
|
79
83
|
];
|
|
80
|
-
const HTTP_SKIPPED_HEADERS = [
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const HTTP_HEADER_VARY = Buffer.from(
|
|
87
|
-
const HTTP_HEADER_TRANSFER_ENCODING = Buffer.from(
|
|
88
|
-
const CHUNKED = Buffer.from(
|
|
89
|
-
const WILDCARD = Buffer.from(
|
|
90
|
-
const ORIGIN = Buffer.from(
|
|
91
|
-
const X_KUZZLE_REQUEST_ID = Buffer.from(
|
|
92
|
-
const CLOSE = Buffer.from(
|
|
84
|
+
const HTTP_SKIPPED_HEADERS = ["content-length"];
|
|
85
|
+
const HTTP_HEADER_CONNECTION = Buffer.from("Connection");
|
|
86
|
+
const HTTP_HEADER_CONTENT_LENGTH = Buffer.from("Content-Length");
|
|
87
|
+
const HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = Buffer.from(
|
|
88
|
+
"Access-Control-Allow-Origin"
|
|
89
|
+
);
|
|
90
|
+
const HTTP_HEADER_VARY = Buffer.from("Vary");
|
|
91
|
+
const HTTP_HEADER_TRANSFER_ENCODING = Buffer.from("Transfer-Encoding");
|
|
92
|
+
const CHUNKED = Buffer.from("chunked");
|
|
93
|
+
const WILDCARD = Buffer.from("*");
|
|
94
|
+
const ORIGIN = Buffer.from("Origin");
|
|
95
|
+
const X_KUZZLE_REQUEST_ID = Buffer.from("X-Kuzzle-Request-Id");
|
|
96
|
+
const CLOSE = Buffer.from("close");
|
|
93
97
|
const CHARSET_REGEX = /charset=([\w-]+)/i;
|
|
94
98
|
|
|
95
99
|
/**
|
|
@@ -97,8 +101,8 @@ const CHARSET_REGEX = /charset=([\w-]+)/i;
|
|
|
97
101
|
* Handles both HTTP and WebSocket connections
|
|
98
102
|
*/
|
|
99
103
|
class HttpWsProtocol extends Protocol {
|
|
100
|
-
constructor
|
|
101
|
-
super(
|
|
104
|
+
constructor() {
|
|
105
|
+
super("websocket");
|
|
102
106
|
|
|
103
107
|
this.server = null;
|
|
104
108
|
this.wsConfig = null;
|
|
@@ -120,7 +124,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
120
124
|
this.socketByConnectionId = new Map();
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
async init
|
|
127
|
+
async init(entrypoint) {
|
|
124
128
|
super.init(null, entrypoint);
|
|
125
129
|
|
|
126
130
|
this.config = entrypoint.config.protocols;
|
|
@@ -128,7 +132,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
128
132
|
this.wsConfig = this.parseWebSocketOptions();
|
|
129
133
|
this.httpConfig = this.parseHttpOptions();
|
|
130
134
|
|
|
131
|
-
if (!
|
|
135
|
+
if (!this.wsConfig.enabled && !this.httpConfig.enabled) {
|
|
132
136
|
return false;
|
|
133
137
|
}
|
|
134
138
|
|
|
@@ -143,18 +147,20 @@ class HttpWsProtocol extends Protocol {
|
|
|
143
147
|
this.initHttp();
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
this.server.listen(entrypoint.config.port, socket => {
|
|
147
|
-
if (!
|
|
148
|
-
throw new Error(
|
|
150
|
+
this.server.listen(entrypoint.config.port, (socket) => {
|
|
151
|
+
if (!socket) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`[http/websocket] fatal: unable to listen to port ${entrypoint.config.port}`
|
|
154
|
+
);
|
|
149
155
|
}
|
|
150
156
|
});
|
|
151
157
|
|
|
152
158
|
return true;
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
initWebSocket
|
|
161
|
+
initWebSocket() {
|
|
156
162
|
/* eslint-disable sort-keys */
|
|
157
|
-
this.server.ws(
|
|
163
|
+
this.server.ws("/*", {
|
|
158
164
|
...this.wsConfig.opts,
|
|
159
165
|
maxBackPressure: WS_PER_SOCKET_BACKPRESSURE_BUFFER_SIZE,
|
|
160
166
|
upgrade: this.wsOnUpgradeHandler.bind(this),
|
|
@@ -166,11 +172,11 @@ class HttpWsProtocol extends Protocol {
|
|
|
166
172
|
/* eslint-enable sort-keys */
|
|
167
173
|
}
|
|
168
174
|
|
|
169
|
-
initHttp
|
|
170
|
-
this.server.any(
|
|
175
|
+
initHttp() {
|
|
176
|
+
this.server.any("/*", this.httpOnMessageHandler.bind(this));
|
|
171
177
|
}
|
|
172
178
|
|
|
173
|
-
broadcast
|
|
179
|
+
broadcast(data) {
|
|
174
180
|
const stringified = JSON.stringify(data.payload);
|
|
175
181
|
const payloadByteSize = Buffer.from(stringified).byteLength;
|
|
176
182
|
// 255 bytes should be enough to hold the following:
|
|
@@ -197,16 +203,20 @@ class HttpWsProtocol extends Protocol {
|
|
|
197
203
|
|
|
198
204
|
payload.copy(payloadSafeCopy, 0, 0, payloadLength);
|
|
199
205
|
|
|
200
|
-
debugWS(
|
|
206
|
+
debugWS(
|
|
207
|
+
'Publishing to channel "realtime/%s": %s',
|
|
208
|
+
channel,
|
|
209
|
+
payloadSafeCopy
|
|
210
|
+
);
|
|
201
211
|
this.server.publish(`realtime/${channel}`, payloadSafeCopy, false);
|
|
202
212
|
}
|
|
203
213
|
}
|
|
204
214
|
|
|
205
|
-
notify
|
|
215
|
+
notify(data) {
|
|
206
216
|
const socket = this.socketByConnectionId.get(data.connectionId);
|
|
207
|
-
debugWS(
|
|
217
|
+
debugWS("notify: %a", data);
|
|
208
218
|
|
|
209
|
-
if (!
|
|
219
|
+
if (!socket) {
|
|
210
220
|
return;
|
|
211
221
|
}
|
|
212
222
|
|
|
@@ -218,44 +228,53 @@ class HttpWsProtocol extends Protocol {
|
|
|
218
228
|
}
|
|
219
229
|
}
|
|
220
230
|
|
|
221
|
-
joinChannel
|
|
231
|
+
joinChannel(channel, connectionId) {
|
|
222
232
|
const socket = this.socketByConnectionId.get(connectionId);
|
|
223
233
|
|
|
224
|
-
if (!
|
|
234
|
+
if (!socket) {
|
|
225
235
|
return;
|
|
226
236
|
}
|
|
227
237
|
|
|
228
|
-
debugWS(
|
|
238
|
+
debugWS(
|
|
239
|
+
'Subscribing connection ID "%s" to channel "realtime/%s"',
|
|
240
|
+
connectionId,
|
|
241
|
+
channel
|
|
242
|
+
);
|
|
229
243
|
socket.subscribe(`realtime/${channel}`);
|
|
230
244
|
}
|
|
231
245
|
|
|
232
|
-
leaveChannel
|
|
246
|
+
leaveChannel(channel, connectionId) {
|
|
233
247
|
const socket = this.socketByConnectionId.get(connectionId);
|
|
234
248
|
|
|
235
|
-
if (!
|
|
249
|
+
if (!socket) {
|
|
236
250
|
return;
|
|
237
251
|
}
|
|
238
252
|
|
|
239
|
-
debugWS(
|
|
253
|
+
debugWS(
|
|
254
|
+
'Removing connection ID "%s" from channel "realtime/%s"',
|
|
255
|
+
connectionId,
|
|
256
|
+
channel
|
|
257
|
+
);
|
|
240
258
|
|
|
241
259
|
socket.unsubscribe(`realtime/${channel}`);
|
|
242
260
|
}
|
|
243
261
|
|
|
244
|
-
disconnect
|
|
245
|
-
debugWS(
|
|
262
|
+
disconnect(connectionId, message = null) {
|
|
263
|
+
debugWS("[%s] forced disconnect", connectionId);
|
|
246
264
|
|
|
247
265
|
const socket = this.socketByConnectionId.get(connectionId);
|
|
248
266
|
|
|
249
|
-
if (!
|
|
267
|
+
if (!socket) {
|
|
250
268
|
return;
|
|
251
269
|
}
|
|
252
270
|
|
|
253
271
|
socket.end(
|
|
254
272
|
WS_FORCED_TERMINATION_CODE,
|
|
255
|
-
message ? Buffer.from(message) : WS_GENERIC_CLOSE_MESSAGE
|
|
273
|
+
message ? Buffer.from(message) : WS_GENERIC_CLOSE_MESSAGE
|
|
274
|
+
);
|
|
256
275
|
}
|
|
257
276
|
|
|
258
|
-
wsOnUpgradeHandler
|
|
277
|
+
wsOnUpgradeHandler(res, req, context) {
|
|
259
278
|
const headers = {};
|
|
260
279
|
// Extract headers from uWS request
|
|
261
280
|
req.forEach((header, value) => {
|
|
@@ -266,20 +285,16 @@ class HttpWsProtocol extends Protocol {
|
|
|
266
285
|
{
|
|
267
286
|
headers,
|
|
268
287
|
},
|
|
269
|
-
req.getHeader(
|
|
270
|
-
req.getHeader(
|
|
271
|
-
req.getHeader(
|
|
288
|
+
req.getHeader("sec-websocket-key"),
|
|
289
|
+
req.getHeader("sec-websocket-protocol"),
|
|
290
|
+
req.getHeader("sec-websocket-extensions"),
|
|
272
291
|
context
|
|
273
292
|
);
|
|
274
293
|
}
|
|
275
294
|
|
|
276
|
-
wsOnOpenHandler
|
|
295
|
+
wsOnOpenHandler(socket) {
|
|
277
296
|
const ip = Buffer.from(socket.getRemoteAddressAsText()).toString();
|
|
278
|
-
const connection = new ClientConnection(
|
|
279
|
-
this.name,
|
|
280
|
-
[ip],
|
|
281
|
-
socket.headers
|
|
282
|
-
);
|
|
297
|
+
const connection = new ClientConnection(this.name, [ip], socket.headers);
|
|
283
298
|
|
|
284
299
|
this.entryPoint.newConnection(connection);
|
|
285
300
|
this.connectionBySocket.set(socket, connection);
|
|
@@ -287,19 +302,20 @@ class HttpWsProtocol extends Protocol {
|
|
|
287
302
|
this.backpressureBuffer.set(socket, []);
|
|
288
303
|
}
|
|
289
304
|
|
|
290
|
-
wsOnCloseHandler
|
|
305
|
+
wsOnCloseHandler(socket, code, message) {
|
|
291
306
|
const connection = this.connectionBySocket.get(socket);
|
|
292
307
|
|
|
293
|
-
if (!
|
|
308
|
+
if (!connection) {
|
|
294
309
|
return;
|
|
295
310
|
}
|
|
296
311
|
|
|
297
312
|
if (debugWS.enabled) {
|
|
298
313
|
debugWS(
|
|
299
|
-
|
|
314
|
+
"[%s] received a `close` event (CODE: %d, REASON: %s)",
|
|
300
315
|
connection.id,
|
|
301
316
|
code,
|
|
302
|
-
Buffer.from(message ||
|
|
317
|
+
Buffer.from(message || "").toString()
|
|
318
|
+
);
|
|
303
319
|
}
|
|
304
320
|
this.entryPoint.removeConnection(connection.id);
|
|
305
321
|
this.connectionBySocket.delete(socket);
|
|
@@ -307,8 +323,8 @@ class HttpWsProtocol extends Protocol {
|
|
|
307
323
|
this.socketByConnectionId.delete(connection.id);
|
|
308
324
|
}
|
|
309
325
|
|
|
310
|
-
wsOnMessageHandler
|
|
311
|
-
if (!
|
|
326
|
+
wsOnMessageHandler(socket, data) {
|
|
327
|
+
if (!data || data.byteLength === 0) {
|
|
312
328
|
return;
|
|
313
329
|
}
|
|
314
330
|
|
|
@@ -323,8 +339,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
323
339
|
this.wsSendError(socket, connection, WS_RATE_LIMIT_EXCEEDED_ERROR);
|
|
324
340
|
return;
|
|
325
341
|
}
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
342
|
+
} else {
|
|
328
343
|
socket.last = this.now;
|
|
329
344
|
socket.count = 1;
|
|
330
345
|
}
|
|
@@ -333,12 +348,11 @@ class HttpWsProtocol extends Protocol {
|
|
|
333
348
|
let parsed;
|
|
334
349
|
const message = Buffer.from(data).toString();
|
|
335
350
|
|
|
336
|
-
debugWS(
|
|
351
|
+
debugWS("[%s] client message: %s", connection.id, message);
|
|
337
352
|
|
|
338
353
|
try {
|
|
339
354
|
parsed = JSON.parse(message);
|
|
340
|
-
}
|
|
341
|
-
catch (e) {
|
|
355
|
+
} catch (e) {
|
|
342
356
|
/*
|
|
343
357
|
we cannot add a "room" information since we need to extract
|
|
344
358
|
a request ID from the incoming data, which is apparently
|
|
@@ -349,7 +363,8 @@ class HttpWsProtocol extends Protocol {
|
|
|
349
363
|
this.wsSendError(
|
|
350
364
|
socket,
|
|
351
365
|
connection,
|
|
352
|
-
kerrorWS.getFrom(e,
|
|
366
|
+
kerrorWS.getFrom(e, "unexpected_error", e.message)
|
|
367
|
+
);
|
|
353
368
|
return;
|
|
354
369
|
}
|
|
355
370
|
|
|
@@ -363,20 +378,18 @@ class HttpWsProtocol extends Protocol {
|
|
|
363
378
|
|
|
364
379
|
try {
|
|
365
380
|
request = new Request(parsed, { connection });
|
|
366
|
-
}
|
|
367
|
-
catch (e) {
|
|
381
|
+
} catch (e) {
|
|
368
382
|
this.wsSendError(socket, connection, e, parsed.requestId);
|
|
369
383
|
return;
|
|
370
384
|
}
|
|
371
385
|
|
|
372
|
-
this.entryPoint.execute(connection, request, result => {
|
|
386
|
+
this.entryPoint.execute(connection, request, (result) => {
|
|
373
387
|
if (result.content) {
|
|
374
|
-
if (typeof result.content ===
|
|
388
|
+
if (typeof result.content === "object") {
|
|
375
389
|
result.content.room = result.requestId;
|
|
376
390
|
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
result.content = '';
|
|
391
|
+
} else {
|
|
392
|
+
result.content = "";
|
|
380
393
|
}
|
|
381
394
|
|
|
382
395
|
this.wsSend(socket, Buffer.from(JSON.stringify(result.content)));
|
|
@@ -387,12 +400,13 @@ class HttpWsProtocol extends Protocol {
|
|
|
387
400
|
* Absorb as much of the backpressure buffer as possible
|
|
388
401
|
* @param {uWS.WebSocket} socket
|
|
389
402
|
*/
|
|
390
|
-
wsOnDrainHandler
|
|
403
|
+
wsOnDrainHandler(socket) {
|
|
391
404
|
socket.cork(() => {
|
|
392
405
|
const buffer = this.backpressureBuffer.get(socket);
|
|
393
406
|
|
|
394
|
-
while (
|
|
395
|
-
|
|
407
|
+
while (
|
|
408
|
+
buffer.length > 0 &&
|
|
409
|
+
socket.getBufferedAmount() < WS_PER_SOCKET_BACKPRESSURE_BUFFER_SIZE
|
|
396
410
|
) {
|
|
397
411
|
const payload = buffer.shift();
|
|
398
412
|
socket.send(payload);
|
|
@@ -408,7 +422,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
408
422
|
* @param {Error} error
|
|
409
423
|
* @param {String} requestId @optional
|
|
410
424
|
*/
|
|
411
|
-
wsSendError
|
|
425
|
+
wsSendError(socket, connection, error, requestId) {
|
|
412
426
|
const request = new Request({}, { connection, error });
|
|
413
427
|
|
|
414
428
|
// If a requestId is provided we use it instead of the generated one
|
|
@@ -426,15 +440,14 @@ class HttpWsProtocol extends Protocol {
|
|
|
426
440
|
* @param {uWS.WebSocket} socket
|
|
427
441
|
* @param {Buffer} payload
|
|
428
442
|
*/
|
|
429
|
-
wsSend
|
|
430
|
-
if (!
|
|
443
|
+
wsSend(socket, payload) {
|
|
444
|
+
if (!this.connectionBySocket.has(socket)) {
|
|
431
445
|
return;
|
|
432
446
|
}
|
|
433
447
|
|
|
434
448
|
if (socket.getBufferedAmount() < WS_PER_SOCKET_BACKPRESSURE_BUFFER_SIZE) {
|
|
435
449
|
socket.cork(() => socket.send(payload));
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
450
|
+
} else {
|
|
438
451
|
const buffer = this.backpressureBuffer.get(socket);
|
|
439
452
|
buffer.push(payload);
|
|
440
453
|
|
|
@@ -449,43 +462,49 @@ class HttpWsProtocol extends Protocol {
|
|
|
449
462
|
* @param {uWS.HttpResponse} response
|
|
450
463
|
* @param {uWS.HttpRequest} request
|
|
451
464
|
*/
|
|
452
|
-
httpOnMessageHandler
|
|
465
|
+
httpOnMessageHandler(response, request) {
|
|
453
466
|
const connection = new ClientConnection(
|
|
454
|
-
|
|
467
|
+
"HTTP/1.1",
|
|
455
468
|
getHttpIps(response, request),
|
|
456
|
-
request.headers
|
|
469
|
+
request.headers
|
|
470
|
+
);
|
|
457
471
|
const message = new HttpMessage(connection, request);
|
|
458
472
|
|
|
459
|
-
debugHTTP(
|
|
473
|
+
debugHTTP("[%s] Received HTTP request: %a", connection.id, message);
|
|
460
474
|
|
|
461
|
-
if (message.headers[
|
|
475
|
+
if (message.headers["content-length"] > this.maxRequestSize) {
|
|
462
476
|
this.httpSendError(message, response, HTTP_REQUEST_TOO_LARGE_ERROR);
|
|
463
477
|
return;
|
|
464
478
|
}
|
|
465
479
|
|
|
466
|
-
const contentType = message.headers[
|
|
480
|
+
const contentType = message.headers["content-type"];
|
|
467
481
|
|
|
468
|
-
if (
|
|
469
|
-
&&
|
|
482
|
+
if (
|
|
483
|
+
contentType &&
|
|
484
|
+
!HTTP_ALLOWED_CONTENT_TYPES.some((allowed) =>
|
|
485
|
+
contentType.includes(allowed)
|
|
486
|
+
)
|
|
470
487
|
) {
|
|
471
488
|
this.httpSendError(
|
|
472
489
|
message,
|
|
473
490
|
response,
|
|
474
|
-
kerrorHTTP.get(
|
|
491
|
+
kerrorHTTP.get("unsupported_content", contentType)
|
|
492
|
+
);
|
|
475
493
|
return;
|
|
476
494
|
}
|
|
477
495
|
|
|
478
496
|
const encoding = CHARSET_REGEX.exec(contentType);
|
|
479
497
|
|
|
480
|
-
if (encoding !== null && encoding[1].toLowerCase() !==
|
|
498
|
+
if (encoding !== null && encoding[1].toLowerCase() !== "utf-8") {
|
|
481
499
|
this.httpSendError(
|
|
482
500
|
message,
|
|
483
501
|
response,
|
|
484
|
-
kerrorHTTP.get(
|
|
502
|
+
kerrorHTTP.get("unsupported_charset", encoding[1].toLowerCase())
|
|
503
|
+
);
|
|
485
504
|
return;
|
|
486
505
|
}
|
|
487
506
|
|
|
488
|
-
this.httpReadData(message, response, err => {
|
|
507
|
+
this.httpReadData(message, response, (err) => {
|
|
489
508
|
if (err) {
|
|
490
509
|
this.httpSendError(message, response, err);
|
|
491
510
|
return;
|
|
@@ -502,7 +521,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
502
521
|
* @param {uWS.HttpResponse} response
|
|
503
522
|
* @param {Function} cb
|
|
504
523
|
*/
|
|
505
|
-
httpReadData
|
|
524
|
+
httpReadData(message, response, cb) {
|
|
506
525
|
let payload = null;
|
|
507
526
|
response.aborted = false;
|
|
508
527
|
|
|
@@ -523,7 +542,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
523
542
|
return;
|
|
524
543
|
}
|
|
525
544
|
|
|
526
|
-
if (!
|
|
545
|
+
if (!isLast) {
|
|
527
546
|
return;
|
|
528
547
|
}
|
|
529
548
|
|
|
@@ -549,14 +568,14 @@ class HttpWsProtocol extends Protocol {
|
|
|
549
568
|
});
|
|
550
569
|
}
|
|
551
570
|
|
|
552
|
-
httpParseContent
|
|
553
|
-
const type = message.headers[
|
|
571
|
+
httpParseContent(message, content, cb) {
|
|
572
|
+
const type = message.headers["content-type"] || "";
|
|
554
573
|
|
|
555
|
-
if (type.includes(
|
|
556
|
-
const parts = uWS.getParts(content, message.headers[
|
|
574
|
+
if (type.includes("multipart/form-data")) {
|
|
575
|
+
const parts = uWS.getParts(content, message.headers["content-type"]);
|
|
557
576
|
message.content = {};
|
|
558
|
-
|
|
559
|
-
if (!
|
|
577
|
+
|
|
578
|
+
if (!parts) {
|
|
560
579
|
cb();
|
|
561
580
|
return;
|
|
562
581
|
}
|
|
@@ -570,24 +589,22 @@ class HttpWsProtocol extends Protocol {
|
|
|
570
589
|
if (part.filename) {
|
|
571
590
|
message.content[part.name] = {
|
|
572
591
|
encoding: part.type,
|
|
573
|
-
file: Buffer.from(part.data).toString(
|
|
592
|
+
file: Buffer.from(part.data).toString("base64"),
|
|
574
593
|
filename: part.filename,
|
|
575
594
|
};
|
|
576
|
-
}
|
|
577
|
-
else {
|
|
595
|
+
} else {
|
|
578
596
|
message.content[part.name] = Buffer.from(part.data).toString();
|
|
579
597
|
}
|
|
580
598
|
}
|
|
581
|
-
}
|
|
582
|
-
else if (type.includes('application/x-www-form-urlencoded')) {
|
|
599
|
+
} else if (type.includes("application/x-www-form-urlencoded")) {
|
|
583
600
|
message.content = querystring.parse(content.toString());
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
601
|
+
} else {
|
|
586
602
|
try {
|
|
587
603
|
message.content = JSON.parse(content.toString());
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
604
|
+
} catch (e) {
|
|
605
|
+
cb(
|
|
606
|
+
kerrorHTTP.get("body_parse_failed", content.toString().slice(0, 50))
|
|
607
|
+
);
|
|
591
608
|
return;
|
|
592
609
|
}
|
|
593
610
|
}
|
|
@@ -595,8 +612,8 @@ class HttpWsProtocol extends Protocol {
|
|
|
595
612
|
cb();
|
|
596
613
|
}
|
|
597
614
|
|
|
598
|
-
httpProcessRequest
|
|
599
|
-
debugHTTP(
|
|
615
|
+
httpProcessRequest(response, message) {
|
|
616
|
+
debugHTTP("[%s] httpProcessRequest: %a", message.connection.id, message);
|
|
600
617
|
|
|
601
618
|
if (response.aborted) {
|
|
602
619
|
return;
|
|
@@ -604,7 +621,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
604
621
|
|
|
605
622
|
this.entryPoint.newConnection(message.connection);
|
|
606
623
|
|
|
607
|
-
global.kuzzle.router.http.route(message, request => {
|
|
624
|
+
global.kuzzle.router.http.route(message, (request) => {
|
|
608
625
|
this.entryPoint.logAccess(message.connection, request, message);
|
|
609
626
|
this.entryPoint.removeConnection(message.connection.id);
|
|
610
627
|
|
|
@@ -621,26 +638,28 @@ class HttpWsProtocol extends Protocol {
|
|
|
621
638
|
|
|
622
639
|
const data = this.httpRequestToResponse(request, message);
|
|
623
640
|
|
|
624
|
-
this.httpCompress(message, data, result => {
|
|
641
|
+
this.httpCompress(message, data, (result) => {
|
|
625
642
|
if (response.aborted) {
|
|
626
643
|
return;
|
|
627
644
|
}
|
|
628
645
|
|
|
629
|
-
request.response.setHeader(
|
|
646
|
+
request.response.setHeader("Content-Encoding", result.encoding);
|
|
630
647
|
|
|
631
648
|
response.cork(() => {
|
|
632
649
|
this.httpWriteRequestHeaders(request, response, message);
|
|
633
650
|
|
|
634
|
-
const [
|
|
651
|
+
const [success] = response.tryEnd(
|
|
635
652
|
result.compressed,
|
|
636
|
-
result.compressed.length
|
|
653
|
+
result.compressed.length
|
|
654
|
+
);
|
|
637
655
|
|
|
638
|
-
if (!
|
|
639
|
-
response.onWritable(offset => {
|
|
656
|
+
if (!success) {
|
|
657
|
+
response.onWritable((offset) => {
|
|
640
658
|
const retryData = result.compressed.subarray(offset);
|
|
641
|
-
const [
|
|
659
|
+
const [retrySuccess] = response.tryEnd(
|
|
642
660
|
retryData,
|
|
643
|
-
retryData.length
|
|
661
|
+
retryData.length
|
|
662
|
+
);
|
|
644
663
|
|
|
645
664
|
return retrySuccess;
|
|
646
665
|
});
|
|
@@ -656,7 +675,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
656
675
|
* @param {uWS.HttpResponse} response
|
|
657
676
|
* @param {HttpMessage} message
|
|
658
677
|
*/
|
|
659
|
-
httpWriteRequestHeaders
|
|
678
|
+
httpWriteRequestHeaders(request, response, message) {
|
|
660
679
|
response.writeStatus(Buffer.from(request.response.status.toString()));
|
|
661
680
|
|
|
662
681
|
response.writeHeader(HTTP_HEADER_CONNECTION, CLOSE);
|
|
@@ -670,13 +689,15 @@ class HttpWsProtocol extends Protocol {
|
|
|
670
689
|
}
|
|
671
690
|
|
|
672
691
|
// Access-Control-Allow-Origin Logic
|
|
673
|
-
if (
|
|
674
|
-
|
|
675
|
-
|
|
692
|
+
if (
|
|
693
|
+
request.response.headers["Access-Control-Allow-Origin"] === undefined &&
|
|
694
|
+
message.headers &&
|
|
695
|
+
message.headers.origin
|
|
676
696
|
) {
|
|
677
697
|
response.writeHeader(
|
|
678
698
|
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
679
|
-
Buffer.from(message.headers.origin)
|
|
699
|
+
Buffer.from(message.headers.origin)
|
|
700
|
+
);
|
|
680
701
|
|
|
681
702
|
response.writeHeader(HTTP_HEADER_VARY, ORIGIN);
|
|
682
703
|
}
|
|
@@ -698,14 +719,20 @@ class HttpWsProtocol extends Protocol {
|
|
|
698
719
|
* @param {uWS.HttpResponse} response
|
|
699
720
|
* @param {HttpMessage} message
|
|
700
721
|
*/
|
|
701
|
-
httpSendStream
|
|
702
|
-
const streamSizeFixed =
|
|
722
|
+
httpSendStream(request, response, httpStream, message) {
|
|
723
|
+
const streamSizeFixed =
|
|
724
|
+
typeof httpStream.totalBytes === "number" && httpStream.totalBytes > 0;
|
|
703
725
|
|
|
704
726
|
if (httpStream.errored) {
|
|
705
727
|
this.httpSendError(
|
|
706
728
|
message,
|
|
707
729
|
response,
|
|
708
|
-
kerror.get(
|
|
730
|
+
kerror.get(
|
|
731
|
+
"network",
|
|
732
|
+
"http",
|
|
733
|
+
"stream_errored",
|
|
734
|
+
httpStream.error.message
|
|
735
|
+
)
|
|
709
736
|
);
|
|
710
737
|
return;
|
|
711
738
|
}
|
|
@@ -714,29 +741,34 @@ class HttpWsProtocol extends Protocol {
|
|
|
714
741
|
this.httpSendError(
|
|
715
742
|
message,
|
|
716
743
|
response,
|
|
717
|
-
kerror.get(
|
|
744
|
+
kerror.get("network", "http", "stream_closed")
|
|
718
745
|
);
|
|
719
746
|
return;
|
|
720
747
|
}
|
|
721
748
|
|
|
722
749
|
// Remove Content-Length header to avoid conflic with Transfer-Encoding header
|
|
723
|
-
request.response.setHeader(
|
|
750
|
+
request.response.setHeader("Content-Length", null);
|
|
724
751
|
|
|
725
752
|
// Send Headers in one go
|
|
726
753
|
response.cork(() => {
|
|
727
754
|
this.httpWriteRequestHeaders(request, response, message);
|
|
728
755
|
|
|
729
756
|
if (streamSizeFixed) {
|
|
730
|
-
response.writeHeader(
|
|
731
|
-
|
|
732
|
-
|
|
757
|
+
response.writeHeader(
|
|
758
|
+
HTTP_HEADER_CONTENT_LENGTH,
|
|
759
|
+
Buffer.from(httpStream.totalBytes.toString())
|
|
760
|
+
);
|
|
761
|
+
} else {
|
|
733
762
|
response.writeHeader(HTTP_HEADER_TRANSFER_ENCODING, CHUNKED);
|
|
734
763
|
}
|
|
735
764
|
});
|
|
736
765
|
|
|
737
|
-
httpStream.stream.on(
|
|
766
|
+
httpStream.stream.on("data", (chunk) => {
|
|
738
767
|
// Make a copy of the array buffer
|
|
739
|
-
const arrayBuffer = chunk.buffer.slice(
|
|
768
|
+
const arrayBuffer = chunk.buffer.slice(
|
|
769
|
+
chunk.byteOffset,
|
|
770
|
+
chunk.byteOffset + chunk.byteLength
|
|
771
|
+
);
|
|
740
772
|
|
|
741
773
|
const arrayBufferOffset = response.getWriteOffset();
|
|
742
774
|
|
|
@@ -748,18 +780,20 @@ class HttpWsProtocol extends Protocol {
|
|
|
748
780
|
* If the stream has a fixed size we can use the tryEnd method
|
|
749
781
|
*/
|
|
750
782
|
if (streamSizeFixed) {
|
|
751
|
-
const [
|
|
752
|
-
|
|
783
|
+
const [success, done] = response.tryEnd(
|
|
784
|
+
arrayBuffer,
|
|
785
|
+
httpStream.totalBytes
|
|
786
|
+
);
|
|
787
|
+
backpressure = !success;
|
|
753
788
|
|
|
754
789
|
if (done) {
|
|
755
790
|
httpStream.destroy();
|
|
756
791
|
return;
|
|
757
792
|
}
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
793
|
+
} else {
|
|
760
794
|
const success = response.write(arrayBuffer);
|
|
761
795
|
|
|
762
|
-
backpressure = !
|
|
796
|
+
backpressure = !success;
|
|
763
797
|
}
|
|
764
798
|
|
|
765
799
|
// In case of backpressure we need to wait for drainage before sending more data
|
|
@@ -772,59 +806,66 @@ class HttpWsProtocol extends Protocol {
|
|
|
772
806
|
* When the stream is drained we can resume sending data,
|
|
773
807
|
* only if the is no backpressure after we wrote the missing chunk data.
|
|
774
808
|
*/
|
|
775
|
-
response.onWritable(offset => {
|
|
776
|
-
if (!
|
|
809
|
+
response.onWritable((offset) => {
|
|
810
|
+
if (!streamSizeFixed && offset - response.arrayBufferOffset === 0) {
|
|
777
811
|
httpStream.stream.resume();
|
|
778
812
|
return true;
|
|
779
813
|
}
|
|
780
814
|
|
|
781
815
|
let retryBackpressure = false;
|
|
782
|
-
const remainingChunkData = response.arrayBuffer.slice(
|
|
816
|
+
const remainingChunkData = response.arrayBuffer.slice(
|
|
817
|
+
offset - response.arrayBufferOffset
|
|
818
|
+
);
|
|
783
819
|
|
|
784
820
|
if (streamSizeFixed) {
|
|
785
|
-
const [
|
|
821
|
+
const [success] = response.tryEnd(
|
|
822
|
+
remainingChunkData,
|
|
823
|
+
httpStream.totalBytes
|
|
824
|
+
);
|
|
786
825
|
|
|
787
|
-
retryBackpressure = !
|
|
788
|
-
}
|
|
789
|
-
else {
|
|
826
|
+
retryBackpressure = !success;
|
|
827
|
+
} else {
|
|
790
828
|
const success = response.write(remainingChunkData);
|
|
791
829
|
|
|
792
|
-
retryBackpressure = !
|
|
830
|
+
retryBackpressure = !success;
|
|
793
831
|
}
|
|
794
832
|
|
|
795
|
-
if (!
|
|
833
|
+
if (!retryBackpressure) {
|
|
796
834
|
httpStream.stream.resume();
|
|
797
835
|
}
|
|
798
836
|
|
|
799
|
-
return !
|
|
837
|
+
return !retryBackpressure;
|
|
800
838
|
});
|
|
801
839
|
}
|
|
802
840
|
});
|
|
803
841
|
|
|
804
|
-
httpStream.stream.on(
|
|
842
|
+
httpStream.stream.on("end", () => {
|
|
805
843
|
if (httpStream.destroy()) {
|
|
806
844
|
response.end();
|
|
807
845
|
}
|
|
808
846
|
});
|
|
809
847
|
|
|
810
|
-
httpStream.stream.on(
|
|
848
|
+
httpStream.stream.on("close", () => {
|
|
811
849
|
if (httpStream.destroy()) {
|
|
812
850
|
response.end();
|
|
813
851
|
}
|
|
814
852
|
});
|
|
815
853
|
|
|
816
|
-
httpStream.stream.on(
|
|
854
|
+
httpStream.stream.on("error", (err) => {
|
|
817
855
|
if (httpStream.destroy()) {
|
|
818
856
|
response.end();
|
|
819
857
|
}
|
|
820
858
|
|
|
821
|
-
debugHTTP(
|
|
859
|
+
debugHTTP(
|
|
860
|
+
"[%s] httpSendStream: %s",
|
|
861
|
+
httpStream.connection.id,
|
|
862
|
+
err.message
|
|
863
|
+
);
|
|
822
864
|
});
|
|
823
865
|
|
|
824
866
|
response.onAborted(() => {
|
|
825
867
|
httpStream.destroy();
|
|
826
868
|
});
|
|
827
|
-
|
|
828
869
|
}
|
|
829
870
|
|
|
830
871
|
/**
|
|
@@ -834,14 +875,15 @@ class HttpWsProtocol extends Protocol {
|
|
|
834
875
|
* @param {uWS.HttpResponse} response
|
|
835
876
|
* @param {Error} error
|
|
836
877
|
*/
|
|
837
|
-
httpSendError
|
|
838
|
-
const kerr =
|
|
839
|
-
|
|
840
|
-
|
|
878
|
+
httpSendError(message, response, error) {
|
|
879
|
+
const kerr =
|
|
880
|
+
error instanceof KuzzleError
|
|
881
|
+
? error
|
|
882
|
+
: kerrorHTTP.getFrom(error, "unexpected_error", error.message);
|
|
841
883
|
|
|
842
884
|
const content = Buffer.from(JSON.stringify(removeStacktrace(kerr)));
|
|
843
885
|
|
|
844
|
-
debugHTTP(
|
|
886
|
+
debugHTTP("[%s] httpSendError: %a", message.connection.id, kerr);
|
|
845
887
|
|
|
846
888
|
this.entryPoint.logAccess(
|
|
847
889
|
message.connection,
|
|
@@ -849,7 +891,8 @@ class HttpWsProtocol extends Protocol {
|
|
|
849
891
|
connectionId: message.connection.id,
|
|
850
892
|
error: kerr,
|
|
851
893
|
}),
|
|
852
|
-
message
|
|
894
|
+
message
|
|
895
|
+
);
|
|
853
896
|
this.entryPoint.removeConnection(message.connection.id);
|
|
854
897
|
|
|
855
898
|
if (response.aborted) {
|
|
@@ -866,10 +909,15 @@ class HttpWsProtocol extends Protocol {
|
|
|
866
909
|
// Access-Control-Allow-Origin Logic
|
|
867
910
|
if (message.headers && message.headers.origin) {
|
|
868
911
|
if (global.kuzzle.config.internal.allowAllOrigins) {
|
|
869
|
-
response.writeHeader(
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
912
|
+
response.writeHeader(
|
|
913
|
+
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
914
|
+
WILDCARD
|
|
915
|
+
);
|
|
916
|
+
} else {
|
|
917
|
+
response.writeHeader(
|
|
918
|
+
HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
919
|
+
Buffer.from(message.headers.origin)
|
|
920
|
+
);
|
|
873
921
|
response.writeHeader(HTTP_HEADER_VARY, ORIGIN);
|
|
874
922
|
}
|
|
875
923
|
}
|
|
@@ -886,45 +934,42 @@ class HttpWsProtocol extends Protocol {
|
|
|
886
934
|
* @param {HttpMessage} message
|
|
887
935
|
* @returns {Buffer}
|
|
888
936
|
*/
|
|
889
|
-
httpRequestToResponse
|
|
937
|
+
httpRequestToResponse(request, message) {
|
|
890
938
|
let data = removeStacktrace(request.response.toJSON());
|
|
891
939
|
|
|
892
940
|
if (message.requestId !== data.requestId) {
|
|
893
941
|
data.requestId = message.requestId;
|
|
894
942
|
|
|
895
|
-
if (!
|
|
943
|
+
if (!data.raw) {
|
|
896
944
|
data.content.requestId = message.requestId;
|
|
897
945
|
}
|
|
898
946
|
}
|
|
899
947
|
|
|
900
|
-
debugHTTP(
|
|
948
|
+
debugHTTP("HTTP request response: %a", data);
|
|
901
949
|
|
|
902
950
|
if (data.raw) {
|
|
903
951
|
if (data.content === null || data.content === undefined) {
|
|
904
|
-
data =
|
|
905
|
-
}
|
|
906
|
-
else if (typeof data.content === 'object') {
|
|
952
|
+
data = "";
|
|
953
|
+
} else if (typeof data.content === "object") {
|
|
907
954
|
/*
|
|
908
955
|
This object can be either a Buffer object, a stringified Buffer object,
|
|
909
956
|
or anything else.
|
|
910
957
|
In the former two cases, we create a new Buffer object, and in the
|
|
911
958
|
latter, we stringify t he content.
|
|
912
959
|
*/
|
|
913
|
-
if (
|
|
914
|
-
|
|
960
|
+
if (
|
|
961
|
+
data.content instanceof Buffer ||
|
|
962
|
+
(data.content.type === "Buffer" && Array.isArray(data.content.data))
|
|
915
963
|
) {
|
|
916
964
|
data = data.content;
|
|
917
|
-
}
|
|
918
|
-
else {
|
|
965
|
+
} else {
|
|
919
966
|
data = JSON.stringify(data.content);
|
|
920
967
|
}
|
|
921
|
-
}
|
|
922
|
-
else {
|
|
968
|
+
} else {
|
|
923
969
|
// scalars are sent as strings
|
|
924
970
|
data = data.content.toString();
|
|
925
971
|
}
|
|
926
|
-
}
|
|
927
|
-
else {
|
|
972
|
+
} else {
|
|
928
973
|
let indent = 0;
|
|
929
974
|
const parsedUrl = url.parse(message.url, true);
|
|
930
975
|
|
|
@@ -946,66 +991,70 @@ class HttpWsProtocol extends Protocol {
|
|
|
946
991
|
* @param {Buffer} data
|
|
947
992
|
* @param {Function} callback
|
|
948
993
|
*/
|
|
949
|
-
httpCompress
|
|
950
|
-
if (message.headers[
|
|
951
|
-
const encodings = message.headers[
|
|
952
|
-
.split(
|
|
953
|
-
.map(e => e.trim().toLowerCase());
|
|
994
|
+
httpCompress(message, data, callback) {
|
|
995
|
+
if (message.headers["accept-encoding"]) {
|
|
996
|
+
const encodings = message.headers["accept-encoding"]
|
|
997
|
+
.split(",")
|
|
998
|
+
.map((e) => e.trim().toLowerCase());
|
|
954
999
|
|
|
955
1000
|
let algorithm;
|
|
956
1001
|
let priority = -1;
|
|
957
1002
|
|
|
958
1003
|
for (const encoding of encodings) {
|
|
959
|
-
if (encoding.startsWith(
|
|
960
|
-
let [
|
|
961
|
-
_priority = Number.parseFloat(_priority.split(
|
|
1004
|
+
if (encoding.startsWith("gzip") || encoding.startsWith("deflate")) {
|
|
1005
|
+
let [_algorithm, _priority = "q=0"] = encoding.split(";");
|
|
1006
|
+
_priority = Number.parseFloat(_priority.split("=")[1] || 0);
|
|
962
1007
|
|
|
963
1008
|
if (_priority > priority) {
|
|
964
1009
|
algorithm = _algorithm;
|
|
965
1010
|
priority = _priority;
|
|
966
1011
|
}
|
|
967
1012
|
// gzip should be the preferred algorithm in case of a tie
|
|
968
|
-
else if (
|
|
969
|
-
|
|
1013
|
+
else if (
|
|
1014
|
+
Math.abs(priority - _priority) < 10e-3 &&
|
|
1015
|
+
_algorithm === "gzip"
|
|
970
1016
|
) {
|
|
971
|
-
algorithm =
|
|
1017
|
+
algorithm = "gzip";
|
|
972
1018
|
}
|
|
973
1019
|
}
|
|
974
1020
|
}
|
|
975
1021
|
|
|
976
|
-
if (algorithm ===
|
|
977
|
-
zlib.gzip(data, (err, compressed) =>
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1022
|
+
if (algorithm === "gzip") {
|
|
1023
|
+
zlib.gzip(data, (err, compressed) =>
|
|
1024
|
+
callback({
|
|
1025
|
+
compressed: !err ? compressed : data,
|
|
1026
|
+
encoding: !err ? "gzip" : "identity",
|
|
1027
|
+
})
|
|
1028
|
+
);
|
|
981
1029
|
return;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1030
|
+
} else if (algorithm === "deflate") {
|
|
1031
|
+
zlib.deflate(data, (err, compressed) =>
|
|
1032
|
+
callback({
|
|
1033
|
+
compressed: !err ? compressed : data,
|
|
1034
|
+
encoding: !err ? "deflate" : "identity",
|
|
1035
|
+
})
|
|
1036
|
+
);
|
|
988
1037
|
return;
|
|
989
1038
|
}
|
|
990
1039
|
}
|
|
991
1040
|
|
|
992
1041
|
callback({
|
|
993
1042
|
compressed: data,
|
|
994
|
-
encoding:
|
|
1043
|
+
encoding: "identity",
|
|
995
1044
|
});
|
|
996
1045
|
}
|
|
997
1046
|
|
|
998
|
-
httpUncompress
|
|
999
|
-
let encodings = message.headers[
|
|
1000
|
-
if (!
|
|
1047
|
+
httpUncompress(message, payload, cb) {
|
|
1048
|
+
let encodings = message.headers["content-encoding"];
|
|
1049
|
+
if (!encodings) {
|
|
1001
1050
|
cb(null, payload);
|
|
1002
1051
|
return;
|
|
1003
1052
|
}
|
|
1004
1053
|
|
|
1005
|
-
encodings = encodings.split(
|
|
1054
|
+
encodings = encodings.split(",").map((e) => e.trim().toLowerCase());
|
|
1006
1055
|
|
|
1007
1056
|
if (encodings.length > this.httpConfig.opts.maxEncodingLayers) {
|
|
1008
|
-
cb(kerrorHTTP.get(
|
|
1057
|
+
cb(kerrorHTTP.get("too_many_encodings"));
|
|
1009
1058
|
return;
|
|
1010
1059
|
}
|
|
1011
1060
|
|
|
@@ -1017,7 +1066,7 @@ class HttpWsProtocol extends Protocol {
|
|
|
1017
1066
|
this.httpUncompressStep(encodings, payload, cb, 0);
|
|
1018
1067
|
}
|
|
1019
1068
|
|
|
1020
|
-
httpUncompressStep
|
|
1069
|
+
httpUncompressStep(encodings, payload, cb, index) {
|
|
1021
1070
|
if (index === encodings.length) {
|
|
1022
1071
|
cb(null, payload);
|
|
1023
1072
|
return;
|
|
@@ -1025,11 +1074,10 @@ class HttpWsProtocol extends Protocol {
|
|
|
1025
1074
|
|
|
1026
1075
|
const encoding = encodings[index];
|
|
1027
1076
|
|
|
1028
|
-
if (encoding ===
|
|
1077
|
+
if (encoding === "identity") {
|
|
1029
1078
|
this.httpUncompressStep(encodings, payload, cb, index + 1);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
const fn = encoding === 'gzip' ? zlib.gunzip : zlib.inflate;
|
|
1079
|
+
} else if (encoding === "gzip" || encoding === "deflate") {
|
|
1080
|
+
const fn = encoding === "gzip" ? zlib.gunzip : zlib.inflate;
|
|
1033
1081
|
|
|
1034
1082
|
fn(payload, (err, inflated) => {
|
|
1035
1083
|
if (err) {
|
|
@@ -1039,17 +1087,18 @@ class HttpWsProtocol extends Protocol {
|
|
|
1039
1087
|
|
|
1040
1088
|
this.httpUncompressStep(encodings, inflated, cb, index + 1);
|
|
1041
1089
|
});
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
cb(kerrorHTTP.get('unsupported_compression', encoding));
|
|
1090
|
+
} else {
|
|
1091
|
+
cb(kerrorHTTP.get("unsupported_compression", encoding));
|
|
1045
1092
|
}
|
|
1046
1093
|
}
|
|
1047
1094
|
|
|
1048
|
-
parseWebSocketOptions
|
|
1095
|
+
parseWebSocketOptions() {
|
|
1049
1096
|
const cfg = this.config.websocket;
|
|
1050
1097
|
|
|
1051
1098
|
if (cfg === undefined) {
|
|
1052
|
-
global.kuzzle.log.warn(
|
|
1099
|
+
global.kuzzle.log.warn(
|
|
1100
|
+
"[websocket] no configuration found for websocket: disabling it"
|
|
1101
|
+
);
|
|
1053
1102
|
return { enabled: false };
|
|
1054
1103
|
}
|
|
1055
1104
|
|
|
@@ -1058,14 +1107,18 @@ class HttpWsProtocol extends Protocol {
|
|
|
1058
1107
|
|
|
1059
1108
|
if (idleTimeout === 0 || idleTimeout < 1000) {
|
|
1060
1109
|
idleTimeout = DEFAULT_IDLE_TIMEOUT;
|
|
1061
|
-
global.kuzzle.log.warn(
|
|
1110
|
+
global.kuzzle.log.warn(
|
|
1111
|
+
`[websocket] The "idleTimeout" parameter can neither be deactivated or be set with a value lower than 1000. Defaulted to ${DEFAULT_IDLE_TIMEOUT}.`
|
|
1112
|
+
);
|
|
1062
1113
|
}
|
|
1063
1114
|
|
|
1064
1115
|
/**
|
|
1065
1116
|
* @deprecated -- to be removed in the next major version
|
|
1066
1117
|
*/
|
|
1067
1118
|
if (cfg.heartbeat) {
|
|
1068
|
-
global.kuzzle.log.warn(
|
|
1119
|
+
global.kuzzle.log.warn(
|
|
1120
|
+
'[websocket] The "heartbeat" parameter has been deprecated and is now ignored. The "idleTimeout" parameter should now be configured instead.'
|
|
1121
|
+
);
|
|
1069
1122
|
}
|
|
1070
1123
|
|
|
1071
1124
|
return {
|
|
@@ -1079,20 +1132,22 @@ class HttpWsProtocol extends Protocol {
|
|
|
1079
1132
|
};
|
|
1080
1133
|
}
|
|
1081
1134
|
|
|
1082
|
-
parseHttpOptions
|
|
1135
|
+
parseHttpOptions() {
|
|
1083
1136
|
const cfg = this.config.http;
|
|
1084
1137
|
|
|
1085
1138
|
if (cfg === undefined) {
|
|
1086
|
-
global.kuzzle.log.warn(
|
|
1139
|
+
global.kuzzle.log.warn(
|
|
1140
|
+
"[http] no configuration found for http: disabling it"
|
|
1141
|
+
);
|
|
1087
1142
|
return { enabled: false };
|
|
1088
1143
|
}
|
|
1089
1144
|
|
|
1090
1145
|
// precomputes default headers
|
|
1091
1146
|
const httpCfg = global.kuzzle.config.http;
|
|
1092
1147
|
const headers = [
|
|
1093
|
-
[
|
|
1094
|
-
[
|
|
1095
|
-
[
|
|
1148
|
+
["Access-Control-Allow-Headers", httpCfg.accessControlAllowHeaders],
|
|
1149
|
+
["Access-Control-Allow-Methods", httpCfg.accessControlAllowMethods],
|
|
1150
|
+
["Content-Type", "application/json"],
|
|
1096
1151
|
];
|
|
1097
1152
|
|
|
1098
1153
|
for (const header of headers) {
|
|
@@ -1120,13 +1175,13 @@ class HttpWsProtocol extends Protocol {
|
|
|
1120
1175
|
* @param {uWS.HttpRequest} request
|
|
1121
1176
|
* @return {Array.<string>}
|
|
1122
1177
|
*/
|
|
1123
|
-
function getHttpIps
|
|
1178
|
+
function getHttpIps(response, request) {
|
|
1124
1179
|
const ips = [Buffer.from(response.getRemoteAddressAsText()).toString()];
|
|
1125
1180
|
|
|
1126
|
-
const forwardHeader = request.getHeader(
|
|
1181
|
+
const forwardHeader = request.getHeader("x-forwarded-for");
|
|
1127
1182
|
|
|
1128
1183
|
if (forwardHeader && forwardHeader.length > 0) {
|
|
1129
|
-
for (const header of forwardHeader.split(
|
|
1184
|
+
for (const header of forwardHeader.split(",")) {
|
|
1130
1185
|
const trimmed = header.trim();
|
|
1131
1186
|
|
|
1132
1187
|
if (trimmed.length > 0) {
|