harper 5.1.20 → 5.2.0-alpha.5
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/agent/operations.ts +13 -16
- package/components/Application.ts +188 -7
- package/components/ApplicationScope.ts +3 -0
- package/components/Scope.ts +12 -0
- package/components/anthropic/index.ts +9 -2
- package/components/bedrock/index.ts +25 -2
- package/components/componentLoader.ts +56 -7
- package/components/componentSecrets.ts +451 -0
- package/components/deploymentRecorder.ts +7 -1
- package/components/mcp/adapters/fastify.ts +3 -0
- package/components/mcp/adapters/harperHttp.ts +3 -0
- package/components/mcp/audit.ts +21 -6
- package/components/mcp/customResourceRegistry.ts +17 -38
- package/components/mcp/listChanged.ts +37 -20
- package/components/mcp/quota.ts +145 -0
- package/components/mcp/rateLimit.ts +152 -14
- package/components/mcp/resources.ts +2 -4
- package/components/mcp/toolRegistry.ts +73 -4
- package/components/mcp/tools/application.ts +77 -8
- package/components/mcp/tools/operations.ts +106 -36
- package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
- package/components/mcp/tools/schemas/operations.ts +5 -0
- package/components/mcp/transport.ts +55 -4
- package/components/ollama/index.ts +6 -2
- package/components/openai/index.ts +9 -2
- package/components/operations.js +204 -10
- package/components/operationsValidation.js +173 -0
- package/components/secretOperations.ts +498 -0
- package/config/configUtils.ts +25 -1
- package/config-app.schema.json +81 -0
- package/config-root.schema.json +8 -1
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dataLayer/readAuditLog.ts +14 -0
- package/dataLayer/schemaDescribe.ts +1 -0
- package/dist/agent/operations.d.ts +7 -4
- package/dist/agent/operations.js +13 -15
- package/dist/agent/operations.js.map +1 -1
- package/dist/components/Application.d.ts +25 -2
- package/dist/components/Application.js +166 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +2 -0
- package/dist/components/ApplicationScope.js +3 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -0
- package/dist/components/Scope.js +11 -0
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +4 -1
- package/dist/components/anthropic/index.js +4 -2
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +4 -1
- package/dist/components/bedrock/index.js +22 -2
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -0
- package/dist/components/componentLoader.js +52 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +63 -0
- package/dist/components/componentSecrets.js +405 -0
- package/dist/components/componentSecrets.js.map +1 -0
- package/dist/components/deploymentRecorder.d.ts +3 -0
- package/dist/components/deploymentRecorder.js +4 -3
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/fastify.d.ts +2 -0
- package/dist/components/mcp/adapters/fastify.js +1 -0
- package/dist/components/mcp/adapters/fastify.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
- package/dist/components/mcp/adapters/harperHttp.js +1 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -2
- package/dist/components/mcp/audit.js +20 -5
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
- package/dist/components/mcp/customResourceRegistry.js +15 -30
- package/dist/components/mcp/customResourceRegistry.js.map +1 -1
- package/dist/components/mcp/listChanged.d.ts +7 -2
- package/dist/components/mcp/listChanged.js +28 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/quota.d.ts +31 -0
- package/dist/components/mcp/quota.js +155 -0
- package/dist/components/mcp/quota.js.map +1 -0
- package/dist/components/mcp/rateLimit.d.ts +19 -3
- package/dist/components/mcp/rateLimit.js +129 -13
- package/dist/components/mcp/rateLimit.js.map +1 -1
- package/dist/components/mcp/resources.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +39 -1
- package/dist/components/mcp/toolRegistry.js +60 -5
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.d.ts +8 -1
- package/dist/components/mcp/tools/application.js +65 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +15 -3
- package/dist/components/mcp/tools/operations.js +99 -38
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +4 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/mcp/transport.d.ts +6 -0
- package/dist/components/mcp/transport.js +47 -3
- package/dist/components/mcp/transport.js.map +1 -1
- package/dist/components/ollama/index.d.ts +4 -1
- package/dist/components/ollama/index.js +4 -2
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +4 -1
- package/dist/components/openai/index.js +4 -2
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +39 -0
- package/dist/components/operations.js +194 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.d.ts +38 -0
- package/dist/components/operationsValidation.js +155 -0
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +113 -0
- package/dist/components/secretOperations.js +475 -0
- package/dist/components/secretOperations.js.map +1 -0
- package/dist/config/configUtils.d.ts +4 -1
- package/dist/config/configUtils.js +18 -1
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/dataLayer/readAuditLog.js +11 -0
- package/dist/dataLayer/readAuditLog.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/globals.d.ts +1 -1
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +38 -0
- package/dist/resources/DatabaseTransaction.d.ts +31 -3
- package/dist/resources/DatabaseTransaction.js +190 -29
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +1 -1
- package/dist/resources/LMDBTransaction.js +43 -5
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
- package/dist/resources/PrimaryRocksDatabase.js +194 -0
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
- package/dist/resources/RecordEncoder.d.ts +1 -0
- package/dist/resources/RecordEncoder.js +81 -75
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +19 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.d.ts +0 -1
- package/dist/resources/Resource.js +103 -16
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +6 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -4
- package/dist/resources/Table.js +247 -24
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +25 -28
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/blob.js +165 -22
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/crdt.d.ts +2 -0
- package/dist/resources/crdt.js +45 -36
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +134 -7
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/graphql.js +8 -0
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/jsResource.js +6 -0
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/loadEnv.js +30 -3
- package/dist/resources/loadEnv.js.map +1 -1
- package/dist/resources/login.js +2 -2
- package/dist/resources/login.js.map +1 -1
- package/dist/resources/models/Models.js +14 -3
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/embedHook.js +10 -2
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +56 -0
- package/dist/resources/models/openaiStream.js +94 -0
- package/dist/resources/models/openaiStream.js.map +1 -0
- package/dist/resources/replayLogs.js +4 -3
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/search.d.ts +1 -1
- package/dist/resources/search.js +104 -11
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/secretDecryptor.d.ts +54 -0
- package/dist/resources/secretDecryptor.js +131 -0
- package/dist/resources/secretDecryptor.js.map +1 -0
- package/dist/resources/tracked.js +15 -5
- package/dist/resources/tracked.js.map +1 -1
- package/dist/security/auth.js +71 -8
- package/dist/security/auth.js.map +1 -1
- package/dist/security/jsLoader.js +11 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +9 -1
- package/dist/security/tokenAuthentication.js +31 -1
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
- package/dist/server/DurableSubscriptionsSession.js +8 -4
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +28 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +11 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -3
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +40 -2
- package/dist/server/http.js +560 -77
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +64 -0
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +12 -0
- package/dist/server/liveSubscriptionAuth.js +140 -0
- package/dist/server/liveSubscriptionAuth.js.map +1 -0
- package/dist/server/loadRootComponents.js +2 -1
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +44 -1
- package/dist/server/middlewareChain.js +86 -10
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/mqtt.js +2 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/operationsServer.js +1 -1
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +9 -0
- package/dist/server/serverHelpers/Headers.js +22 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/JSONStream.js +3 -3
- package/dist/server/serverHelpers/JSONStream.js.map +1 -1
- package/dist/server/serverHelpers/Request.d.ts +52 -1
- package/dist/server/serverHelpers/Request.js +122 -1
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +3 -8
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
- package/dist/server/serverHelpers/progressEmitter.js +52 -4
- package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
- package/dist/server/serverHelpers/registeredOperations.js +286 -0
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +11 -5
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
- package/dist/server/serverHelpers/serverUtilities.js +67 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
- package/dist/server/serverHelpers/uwsServer.js +433 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -0
- package/dist/server/static.d.ts +0 -15
- package/dist/server/static.js +161 -2
- package/dist/server/static.js.map +1 -1
- package/dist/server/status/index.d.ts +4 -1
- package/dist/server/status/index.js +64 -4
- package/dist/server/status/index.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +10 -0
- package/dist/server/threads/manageThreads.js +107 -4
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +5 -1
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +92 -14
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-2-0.d.ts +7 -0
- package/dist/upgrade/directives/5-2-0.js +108 -0
- package/dist/upgrade/directives/5-2-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/common_utils.js +5 -0
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/envFile.d.ts +41 -0
- package/dist/utility/envFile.js +221 -0
- package/dist/utility/envFile.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +25 -0
- package/dist/utility/hdbTerms.js +31 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +21 -0
- package/dist/utility/logging/harper_logger.js +160 -8
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logRotator.js +29 -17
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/logging/readLog.d.ts +1 -1
- package/dist/utility/logging/readLog.js +305 -2
- package/dist/utility/logging/readLog.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +18 -0
- package/dist/utility/operationPermissions.js +35 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +17 -0
- package/dist/utility/operation_authorization.js +38 -0
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/secretEnvelope.d.ts +30 -0
- package/dist/utility/secretEnvelope.js +94 -0
- package/dist/utility/secretEnvelope.js.map +1 -0
- package/dist/utility/signalling.d.ts +7 -0
- package/dist/utility/signalling.js +16 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/configValidator.js +70 -24
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +4 -0
- package/json/systemSchema.json +38 -0
- package/npm-shrinkwrap.json +15066 -9347
- package/package.json +13 -8
- package/resources/DESIGN.md +15 -12
- package/resources/DatabaseTransaction.ts +206 -37
- package/resources/LMDBTransaction.ts +43 -9
- package/resources/PrimaryRocksDatabase.ts +201 -0
- package/resources/RecordEncoder.ts +81 -69
- package/resources/RequestTarget.ts +21 -0
- package/resources/Resource.ts +109 -25
- package/resources/ResourceInterface.ts +6 -2
- package/resources/Resources.ts +1 -3
- package/resources/Table.ts +238 -29
- package/resources/analytics/read.ts +25 -30
- package/resources/blob.ts +159 -23
- package/resources/crdt.ts +37 -31
- package/resources/databases.ts +143 -6
- package/resources/graphql.ts +8 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
- package/resources/jsResource.ts +6 -0
- package/resources/loadEnv.ts +34 -3
- package/resources/login.ts +2 -2
- package/resources/models/Models.ts +19 -3
- package/resources/models/embedHook.ts +13 -2
- package/resources/models/openaiStream.ts +133 -0
- package/resources/replayLogs.ts +4 -3
- package/resources/search.ts +113 -11
- package/resources/secretDecryptor.ts +159 -0
- package/resources/tracked.ts +24 -17
- package/security/auth.ts +70 -10
- package/security/jsLoader.ts +12 -0
- package/security/tokenAuthentication.ts +42 -1
- package/server/DESIGN.md +10 -0
- package/server/DurableSubscriptionsSession.ts +8 -4
- package/server/REST.ts +26 -9
- package/server/fastifyRoutes.ts +10 -1
- package/server/graphqlQuerying.ts +4 -3
- package/server/http.ts +563 -77
- package/server/itc/serverHandlers.js +70 -0
- package/server/liveSubscriptionAuth.ts +152 -0
- package/server/loadRootComponents.js +2 -1
- package/server/middlewareChain.ts +107 -17
- package/server/mqtt.ts +2 -1
- package/server/operationsServer.ts +2 -2
- package/server/serverHelpers/Headers.ts +24 -0
- package/server/serverHelpers/JSONStream.ts +3 -3
- package/server/serverHelpers/Request.ts +127 -0
- package/server/serverHelpers/contentTypes.ts +3 -8
- package/server/serverHelpers/progressEmitter.ts +55 -5
- package/server/serverHelpers/registeredOperations.ts +259 -0
- package/server/serverHelpers/serverHandlers.js +11 -5
- package/server/serverHelpers/serverUtilities.ts +88 -5
- package/server/serverHelpers/uwsServer.ts +488 -0
- package/server/static.ts +180 -2
- package/server/status/index.ts +72 -4
- package/server/threads/manageThreads.js +102 -4
- package/server/threads/socketRouter.ts +5 -1
- package/server/threads/threadServer.js +93 -10
- package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
- package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
- package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
- package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
- package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
- package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
- package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
- package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
- package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
- package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
- package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
- package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
- package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
- package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
- package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
- package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-2-0.ts +82 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/common_utils.ts +5 -0
- package/utility/envFile.ts +218 -0
- package/utility/hdbTerms.ts +31 -0
- package/utility/logging/harper_logger.ts +156 -8
- package/utility/logging/logRotator.ts +24 -14
- package/utility/logging/readLog.ts +323 -2
- package/utility/operationPermissions.ts +35 -1
- package/utility/operation_authorization.ts +74 -1
- package/utility/secretEnvelope.ts +113 -0
- package/utility/signalling.ts +15 -0
- package/validation/configValidator.ts +78 -25
- package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
package/dist/server/http.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.bunServeConfigs = exports.universalHeaders = void 0;
|
|
39
|
+
exports.uwsServeConfigs = exports.bunServeConfigs = exports.universalHeaders = void 0;
|
|
40
40
|
exports.registerUdsCleanupPaths = registerUdsCleanupPaths;
|
|
41
41
|
exports.cleanupUdsFiles = cleanupUdsFiles;
|
|
42
42
|
exports.writeUdsMetadata = writeUdsMetadata;
|
|
@@ -47,8 +47,10 @@ exports.deliverSocket = deliverSocket;
|
|
|
47
47
|
exports.proxyRequest = proxyRequest;
|
|
48
48
|
exports.registerServer = registerServer;
|
|
49
49
|
exports.httpServer = httpServer;
|
|
50
|
-
exports.
|
|
50
|
+
exports.registerFastifyInstance = registerFastifyInstance;
|
|
51
|
+
exports.describeMiddlewareChains = describeMiddlewareChains;
|
|
51
52
|
exports.enableProxyProtocol = enableProxyProtocol;
|
|
53
|
+
exports.createH2CProxyFront = createH2CProxyFront;
|
|
52
54
|
exports.logRequest = logRequest;
|
|
53
55
|
exports.getRequestId = getRequestId;
|
|
54
56
|
// @ts-nocheck
|
|
@@ -68,6 +70,7 @@ const keys_ts_1 = require("../security/keys.js");
|
|
|
68
70
|
const node_http2_1 = require("node:http2");
|
|
69
71
|
const node_https_1 = require("node:https");
|
|
70
72
|
const node_http_1 = require("node:http");
|
|
73
|
+
const node_net_2 = require("node:net");
|
|
71
74
|
const Request_ts_1 = require("./serverHelpers/Request.js");
|
|
72
75
|
const Headers_ts_1 = require("./serverHelpers/Headers.js");
|
|
73
76
|
const blob_ts_1 = require("../resources/blob.js");
|
|
@@ -81,7 +84,7 @@ const componentLoader_ts_1 = require("../components/componentLoader.js");
|
|
|
81
84
|
const throttle_ts_1 = require("./throttle.js");
|
|
82
85
|
const middlewareChain_ts_1 = require("./middlewareChain.js");
|
|
83
86
|
const ws_1 = require("ws");
|
|
84
|
-
const { errorToString } = harper_logger_ts_1.default;
|
|
87
|
+
const { errorToString, errorForLog } = harper_logger_ts_1.default;
|
|
85
88
|
Server_ts_1.server.http = httpServer;
|
|
86
89
|
Server_ts_1.server.request = onRequest;
|
|
87
90
|
Server_ts_1.server.ws = onWebSocket;
|
|
@@ -92,8 +95,15 @@ let httpOptions = {};
|
|
|
92
95
|
exports.universalHeaders = [];
|
|
93
96
|
// Bun-specific: stores fetch handler configs per port, used by threadServer.js to call Bun.serve()
|
|
94
97
|
exports.bunServeConfigs = {};
|
|
95
|
-
//
|
|
96
|
-
|
|
98
|
+
// uWS backend (#914): stores { socketPath, secure, handler } configs keyed by UDS path. When
|
|
99
|
+
// HARPER_UWS_UDS is set, the per-worker UDS mirror is served by uWebSockets.js instead of a Node
|
|
100
|
+
// http server; threadServer.js consumes these and calls createUwsServer(). Symphony must forward
|
|
101
|
+
// client identity via X-Forwarded-For (not the PROXY protocol) on these sockets, as the Bun path does.
|
|
102
|
+
exports.uwsServeConfigs = {};
|
|
103
|
+
// Stores non-function listeners (e.g. Fastify servers) per port for fallback delegation on the
|
|
104
|
+
// backends that don't register their own Node http server (Bun, and the uWS HTTP path). Keeping
|
|
105
|
+
// them out of SERVERS is what prevents a competing Node server from binding the same port.
|
|
106
|
+
const fallbackServers = {};
|
|
97
107
|
const udsCleanupPaths = [];
|
|
98
108
|
function registerUdsCleanupPaths(socketPath, yamlPath) {
|
|
99
109
|
udsCleanupPaths.push({ socketPath, yamlPath });
|
|
@@ -111,9 +121,13 @@ function cleanupUdsFiles() {
|
|
|
111
121
|
}
|
|
112
122
|
}
|
|
113
123
|
/** Write YAML metadata for a UDS mirror socket, describing the TLS certs from the corresponding secure server. */
|
|
114
|
-
function writeUdsMetadata(yamlPath, port, secureServer) {
|
|
124
|
+
function writeUdsMetadata(yamlPath, port, secureServer, protocol) {
|
|
115
125
|
const contexts = secureServer.secureContexts;
|
|
116
126
|
let yaml = `pid: ${process.pid}\ntid: ${(0, rocksdb_js_1.currentThreadId)()}\nport: ${port}\n`;
|
|
127
|
+
// Which application protocol this socket speaks (absent = http/1.1, the historical
|
|
128
|
+
// default) — lets a fronting proxy route by negotiated ALPN.
|
|
129
|
+
if (protocol)
|
|
130
|
+
yaml += `protocol: ${protocol}\n`;
|
|
117
131
|
yaml += `certificates:\n`;
|
|
118
132
|
if (contexts?.size > 0) {
|
|
119
133
|
const seen = new Set();
|
|
@@ -359,7 +373,17 @@ function httpServer(listener, options) {
|
|
|
359
373
|
}
|
|
360
374
|
else if (Request_ts_1.isBun) {
|
|
361
375
|
// On Bun, store non-function listeners (e.g. Fastify's http.Server) for fallback delegation
|
|
362
|
-
|
|
376
|
+
fallbackServers[port] = listener;
|
|
377
|
+
}
|
|
378
|
+
else if (httpServers[port]?.uws) {
|
|
379
|
+
// uWS HTTP path (#914, HARPER_UWS_HTTP): the port is backed by uWebSockets.js, not a Node
|
|
380
|
+
// http server, so a raw non-function listener (e.g. Fastify's http.Server via
|
|
381
|
+
// server.http(fastify.server)) must NOT go through registerServer() — that would put it in
|
|
382
|
+
// SERVERS and threadServer would bind a Node http server competing with uWS on the same TCP
|
|
383
|
+
// port. Divert it to the fallback map like the Bun path; makeUwsHandler delegates unhandled
|
|
384
|
+
// requests to it via inject(). The { uws: true } marker is guaranteed present here: the
|
|
385
|
+
// getServer(port) call above (same loop iteration) sets it before this branch runs.
|
|
386
|
+
fallbackServers[port] = listener;
|
|
363
387
|
}
|
|
364
388
|
else {
|
|
365
389
|
listener.isSecure = secure;
|
|
@@ -376,6 +400,29 @@ function getHTTPServer(port, secure, options) {
|
|
|
376
400
|
if (!httpServers[port]) {
|
|
377
401
|
// TODO: These should all come from httpOptions or operationsApiOptions
|
|
378
402
|
const serverPrefix = isOperationsServer ? 'operationsApi_network' : (usageType ?? 'http');
|
|
403
|
+
// uWS plaintext-HTTP path (#914, HARPER_UWS_HTTP): back a non-secure TCP HTTP port with
|
|
404
|
+
// uWebSockets.js directly instead of a Node http server. This is the flag used to run the
|
|
405
|
+
// integration suite through uWS (no symphony/UDS needed). WebSocket upgrades are wired on this
|
|
406
|
+
// path via onWebSocket's uWS branch; it's opt-in and separate from HARPER_UWS_UDS.
|
|
407
|
+
const lastColon = String(port).lastIndexOf(':');
|
|
408
|
+
const uwsPort = lastColon > 0 ? +String(port).slice(lastColon + 1) : +port;
|
|
409
|
+
if (process.env.HARPER_UWS_HTTP &&
|
|
410
|
+
!secure &&
|
|
411
|
+
!isOperationsServer &&
|
|
412
|
+
!String(port).includes('/') &&
|
|
413
|
+
!Number.isNaN(uwsPort)) {
|
|
414
|
+
exports.uwsServeConfigs[port] = {
|
|
415
|
+
port: uwsPort,
|
|
416
|
+
host: lastColon > 0 ? String(port).slice(0, lastColon).replace(/[[\]]/g, '') : undefined,
|
|
417
|
+
secure: false,
|
|
418
|
+
handler: makeUwsHandler(port, isOperationsServer, env.get(serverPrefix + '_requestQueueLimit')),
|
|
419
|
+
};
|
|
420
|
+
// Marker so the httpServers guard is satisfied and the caller has a truthy handle; the
|
|
421
|
+
// actual listen happens in threadServer.js from uwsServeConfigs. onWebSocket() detects
|
|
422
|
+
// this marker (server.uws) and wires native uWS WebSocket handling into the same config.
|
|
423
|
+
httpServers[port] = { uws: true, port };
|
|
424
|
+
return httpServers[port];
|
|
425
|
+
}
|
|
379
426
|
const keepAliveTimeout = env.get(serverPrefix + '_keepAliveTimeout');
|
|
380
427
|
const requestTimeout = env.get(serverPrefix + '_timeout');
|
|
381
428
|
const headersTimeout = env.get(serverPrefix + '_headersTimeout');
|
|
@@ -564,7 +611,9 @@ function getHTTPServer(port, secure, options) {
|
|
|
564
611
|
}
|
|
565
612
|
function onError(error) {
|
|
566
613
|
const headers = error.headers;
|
|
567
|
-
|
|
614
|
+
// the HTTP status may be carried as `statusCode` (our error classes) or `status` (e.g. a thrown plain object)
|
|
615
|
+
const statusCode = error.statusCode ?? error.status;
|
|
616
|
+
const status = statusCode || 500;
|
|
568
617
|
try {
|
|
569
618
|
nodeResponse.writeHead(status, (0, Headers_ts_1.toWriteHeadHeaders)(headers));
|
|
570
619
|
}
|
|
@@ -572,14 +621,14 @@ function getHTTPServer(port, secure, options) {
|
|
|
572
621
|
nodeResponse.end(errorToString(error));
|
|
573
622
|
logRequest(nodeRequest, status, requestId, performance.now() - startTime);
|
|
574
623
|
// a status code is interpreted as an expected error, so just info or warn, otherwise log as error
|
|
575
|
-
if (
|
|
576
|
-
if (
|
|
577
|
-
harper_logger_ts_1.default.warn(error);
|
|
624
|
+
if (statusCode) {
|
|
625
|
+
if (statusCode === 500)
|
|
626
|
+
harper_logger_ts_1.default.warn(errorForLog(error));
|
|
578
627
|
else
|
|
579
|
-
harper_logger_ts_1.default.info(error);
|
|
628
|
+
harper_logger_ts_1.default.info(errorForLog(error));
|
|
580
629
|
}
|
|
581
630
|
else
|
|
582
|
-
harper_logger_ts_1.default.error(error);
|
|
631
|
+
harper_logger_ts_1.default.error(errorForLog(error));
|
|
583
632
|
}
|
|
584
633
|
};
|
|
585
634
|
// create a throttled version of the request handler, so we can throttle POST requests
|
|
@@ -638,34 +687,278 @@ function getHTTPServer(port, secure, options) {
|
|
|
638
687
|
const socketName = `${(0, manageThreads_js_1.getWorkerIndex)()}-${port}`;
|
|
639
688
|
const udsPath = (0, node_path_1.join)(socketsDir, `${socketName}.sock`);
|
|
640
689
|
const yamlPath = (0, node_path_1.join)(socketsDir, `${socketName}.yaml`);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
690
|
+
if (process.env.HARPER_UWS_UDS) {
|
|
691
|
+
// uWS backend (#914): serve the UDS mirror with uWebSockets.js instead of a Node http
|
|
692
|
+
// server. threadServer.js consumes uwsServeConfigs and calls createUwsServer(). uWS does
|
|
693
|
+
// not parse the PROXY protocol, so symphony must use sourceAddressHeader: 'xForwardedFor'
|
|
694
|
+
// for this socket (the same mode it uses for the Bun path).
|
|
695
|
+
exports.uwsServeConfigs[udsPath] = {
|
|
696
|
+
socketPath: udsPath,
|
|
697
|
+
secure: true,
|
|
698
|
+
handler: makeUwsHandler(port, isOperationsServer, env.get(serverPrefix + '_requestQueueLimit')),
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
// Create a plain HTTP server (no TLS) with the same request handler
|
|
703
|
+
const udsServer = (0, node_http_1.createServer)({
|
|
704
|
+
keepAliveTimeout,
|
|
705
|
+
headersTimeout,
|
|
706
|
+
requestTimeout,
|
|
707
|
+
highWaterMark: 128 * 1024,
|
|
708
|
+
noDelay: true,
|
|
709
|
+
keepAlive: true,
|
|
710
|
+
keepAliveInitialDelay: 600,
|
|
711
|
+
maxHeaderSize: env.get(terms.CONFIG_PARAMS.HTTP_MAXHEADERSIZE),
|
|
712
|
+
}, (nodeRequest, nodeResponse) => {
|
|
713
|
+
const method = nodeRequest.method;
|
|
714
|
+
if (method === 'GET' || method === 'OPTIONS' || method === 'HEAD')
|
|
715
|
+
requestHandler(nodeRequest, nodeResponse);
|
|
716
|
+
else
|
|
717
|
+
throttledRequestHandler(nodeRequest, nodeResponse);
|
|
718
|
+
});
|
|
719
|
+
udsServer.isPerThreadSocket = true;
|
|
720
|
+
enableProxyProtocol(udsServer);
|
|
721
|
+
serverRegistry_ts_1.SERVERS[udsPath] = udsServer;
|
|
722
|
+
}
|
|
661
723
|
registerUdsCleanupPaths(udsPath, yamlPath);
|
|
662
724
|
const writeMetadata = () => writeUdsMetadata(yamlPath, port, server);
|
|
663
725
|
options.SNICallback.ready.then(writeMetadata);
|
|
664
726
|
server.secureContextsListeners.push(writeMetadata);
|
|
727
|
+
// Optional cleartext HTTP/2 mirror (spike: HARPER_H2C_UDS=1). A separate socket
|
|
728
|
+
// (`<worker>-<port>-h2.sock`) so a fronting proxy can route by negotiated ALPN:
|
|
729
|
+
// h2 connections here, http/1.1 to the plain mirror above. The metadata yaml
|
|
730
|
+
// carries `protocol: h2` so the proxy can discover which socket speaks what.
|
|
731
|
+
if (process.env.HARPER_H2C_UDS) {
|
|
732
|
+
const udsPathH2 = (0, node_path_1.join)(socketsDir, `${socketName}-h2.sock`);
|
|
733
|
+
const yamlPathH2 = (0, node_path_1.join)(socketsDir, `${socketName}-h2.yaml`);
|
|
734
|
+
const h2Server = (0, node_http2_1.createServer)({}, (nodeRequest, nodeResponse) => {
|
|
735
|
+
const method = nodeRequest.method;
|
|
736
|
+
if (method === 'GET' || method === 'OPTIONS' || method === 'HEAD')
|
|
737
|
+
requestHandler(nodeRequest, nodeResponse);
|
|
738
|
+
else
|
|
739
|
+
throttledRequestHandler(nodeRequest, nodeResponse);
|
|
740
|
+
});
|
|
741
|
+
// A stray non-h2 client (or a truncated preface) fails the session, not the worker.
|
|
742
|
+
h2Server.on('sessionError', (error) => {
|
|
743
|
+
harper_logger_ts_1.default.debug('h2c UDS session error:', error);
|
|
744
|
+
});
|
|
745
|
+
const h2Front = createH2CProxyFront(h2Server);
|
|
746
|
+
h2Front.isPerThreadSocket = true;
|
|
747
|
+
serverRegistry_ts_1.SERVERS[udsPathH2] = h2Front;
|
|
748
|
+
registerUdsCleanupPaths(udsPathH2, yamlPathH2);
|
|
749
|
+
const writeMetadataH2 = () => writeUdsMetadata(yamlPathH2, port, server, 'h2');
|
|
750
|
+
options.SNICallback.ready.then(writeMetadataH2);
|
|
751
|
+
server.secureContextsListeners.push(writeMetadataH2);
|
|
752
|
+
}
|
|
665
753
|
}
|
|
666
754
|
}
|
|
667
755
|
return httpServers[port];
|
|
668
756
|
}
|
|
757
|
+
/**
|
|
758
|
+
* uWS backend (#914): builds the per-request handler for a uWS UDS server. Mirrors the Bun
|
|
759
|
+
* fetchHandler's post-processing (httpChain, unhandled, universalHeaders, Server-Timing,
|
|
760
|
+
* analytics, logging) but returns a plain Harper response descriptor for createUwsServer to
|
|
761
|
+
* serialize onto the uWS HttpResponse. When the chain doesn't handle the request (status === -1)
|
|
762
|
+
* and a Fastify fallback is registered for the port, it delegates via inject() (see injectToFastify),
|
|
763
|
+
* mirroring the Bun path — so legacy Fastify routes work behind uWS too.
|
|
764
|
+
*/
|
|
765
|
+
function makeUwsHandler(port, isOperationsServer, requestQueueLimit) {
|
|
766
|
+
// Build a fresh response descriptor rather than mutating what the chain returned: a handler may
|
|
767
|
+
// return a WHATWG `Response` (read-only `status`/`body` accessors), which the Bun path also never
|
|
768
|
+
// mutates. `headers` is normalized in place the same way the Bun path does.
|
|
769
|
+
const handle = async (request) => {
|
|
770
|
+
const startTime = performance.now();
|
|
771
|
+
let requestId = 0;
|
|
772
|
+
if (isOperationsServer)
|
|
773
|
+
request.isOperationsServer = true;
|
|
774
|
+
if (httpOptions.logging?.id)
|
|
775
|
+
request.requestId = requestId = getRequestId();
|
|
776
|
+
let response = await httpChain[port](request);
|
|
777
|
+
if (!response)
|
|
778
|
+
response = unhandled(request);
|
|
779
|
+
let headers = response.headers;
|
|
780
|
+
if (!headers?.set)
|
|
781
|
+
headers = new Headers_ts_1.Headers(headers);
|
|
782
|
+
for (const [key, value] of exports.universalHeaders)
|
|
783
|
+
headers.set(key, value);
|
|
784
|
+
if (response.status === -1) {
|
|
785
|
+
// The chain didn't handle it. If a Fastify fallback is registered for this port (legacy
|
|
786
|
+
// custom-function routes via server.http(fastify.server)), delegate to it via inject(),
|
|
787
|
+
// mirroring the Bun path; otherwise it's a genuine 404.
|
|
788
|
+
const fastify = fastifyInstances[port];
|
|
789
|
+
if (fastify) {
|
|
790
|
+
const injectResult = await injectToFastify(fastify, {
|
|
791
|
+
method: request.method,
|
|
792
|
+
url: request.url,
|
|
793
|
+
headers: request.headers.asObject,
|
|
794
|
+
body: request.body, // stream; inject() consumes it as the payload
|
|
795
|
+
user: request.user,
|
|
796
|
+
});
|
|
797
|
+
const respHeaders = new Headers_ts_1.Headers();
|
|
798
|
+
for (const [k, v] of Object.entries(injectResult.headers)) {
|
|
799
|
+
if (v == null)
|
|
800
|
+
continue;
|
|
801
|
+
// Keep Set-Cookie multi-valued (Harper Headers + writeHeaders emit each separately);
|
|
802
|
+
// only comma-join other repeated headers.
|
|
803
|
+
if (Array.isArray(v))
|
|
804
|
+
respHeaders.set(k, k.toLowerCase() === 'set-cookie' ? v : v.join(', '));
|
|
805
|
+
else
|
|
806
|
+
respHeaders.set(k, String(v));
|
|
807
|
+
}
|
|
808
|
+
logHttpRequest(request, injectResult.statusCode, requestId, performance.now() - startTime);
|
|
809
|
+
const responseStream = injectResult.stream();
|
|
810
|
+
// Event-stream (SSE) responses must reach the client incrementally — stream the body and,
|
|
811
|
+
// on client disconnect, destroy the inject response so the Fastify reply's teardown runs
|
|
812
|
+
// (matches the Bun path). Finite responses buffer so Content-Length stays set.
|
|
813
|
+
if (String(injectResult.headers['content-type'] ?? '').includes('text/event-stream')) {
|
|
814
|
+
const injectResponse = injectResult.raw?.res;
|
|
815
|
+
if (injectResponse && typeof injectResponse.destroy === 'function') {
|
|
816
|
+
responseStream.once('close', () => injectResponse.destroy());
|
|
817
|
+
}
|
|
818
|
+
return { status: injectResult.statusCode, headers: respHeaders, body: responseStream };
|
|
819
|
+
}
|
|
820
|
+
const chunks = [];
|
|
821
|
+
for await (const chunk of responseStream)
|
|
822
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
823
|
+
return {
|
|
824
|
+
status: injectResult.statusCode,
|
|
825
|
+
headers: respHeaders,
|
|
826
|
+
body: chunks.length > 0 ? Buffer.concat(chunks) : null,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
logHttpRequest(request, 404, requestId, performance.now() - startTime);
|
|
830
|
+
return { status: 404, headers: new Headers_ts_1.Headers({ 'content-type': 'text/plain' }), body: 'Not found\n' };
|
|
831
|
+
}
|
|
832
|
+
const status = response.status || 200;
|
|
833
|
+
const executionTime = performance.now() - startTime;
|
|
834
|
+
if (!response.handlesHeaders) {
|
|
835
|
+
let serverTiming = `hdb;dur=${executionTime.toFixed(2)}`;
|
|
836
|
+
if (response.wasCacheMiss)
|
|
837
|
+
serverTiming += ', miss';
|
|
838
|
+
(0, Headers_ts_1.appendHeader)(headers, 'Server-Timing', serverTiming, true);
|
|
839
|
+
}
|
|
840
|
+
(0, write_ts_1.recordAction)(executionTime, 'duration', request.handlerPath, request.method, response.wasCacheMiss == undefined ? undefined : response.wasCacheMiss ? 'cache-miss' : 'cache-hit');
|
|
841
|
+
(0, write_ts_1.recordActionBinary)(status < 400, 'success', request.handlerPath, request.method);
|
|
842
|
+
(0, write_ts_1.recordActionBinary)(1, 'response_' + status, request.handlerPath, request.method);
|
|
843
|
+
logHttpRequest(request, status, requestId, executionTime);
|
|
844
|
+
// Static handlers (the only handlesHeaders producers) return a `send` SendStream that writes
|
|
845
|
+
// its own headers/body to a Node ServerResponse via .pipe(). uWS has no such object, and a
|
|
846
|
+
// SendStream doesn't start until piped, so streaming it directly hangs (headers never flush).
|
|
847
|
+
// Pipe it into a Writable shim that captures the headers and buffers the file, mirroring the
|
|
848
|
+
// Bun path. Non-handlesHeaders bodies keep streaming through normalizeUwsBody.
|
|
849
|
+
if (response.handlesHeaders && response.body && typeof response.body.pipe === 'function') {
|
|
850
|
+
// send() may return 304 (conditional GET) or 206/416 (Range) — honor the status it set.
|
|
851
|
+
const sent = await bufferSendStream(response.body, headers, status, request.signal);
|
|
852
|
+
return { status: sent.status, headers, handlesHeaders: true, body: sent.body };
|
|
853
|
+
}
|
|
854
|
+
const body = await normalizeUwsBody(response.body, request.signal);
|
|
855
|
+
return { status, headers, handlesHeaders: response.handlesHeaders, body };
|
|
856
|
+
};
|
|
857
|
+
// Shed data-modifying requests when the event queue is backed up (503), mirroring the Node UDS
|
|
858
|
+
// path — GET/OPTIONS/HEAD are cheap and always run, everything else goes through the throttle.
|
|
859
|
+
const throttledHandle = (0, throttle_ts_1.throttle)(handle, (_request) => {
|
|
860
|
+
(0, write_ts_1.recordAction)(true, 'service-unavailable', port);
|
|
861
|
+
return {
|
|
862
|
+
status: 503,
|
|
863
|
+
headers: new Headers_ts_1.Headers({ 'content-type': 'text/plain' }),
|
|
864
|
+
body: 'Service unavailable, exceeded request queue limit',
|
|
865
|
+
};
|
|
866
|
+
}, requestQueueLimit);
|
|
867
|
+
return (request) => {
|
|
868
|
+
const method = request.method;
|
|
869
|
+
if (method === 'GET' || method === 'OPTIONS' || method === 'HEAD')
|
|
870
|
+
return handle(request);
|
|
871
|
+
return throttledHandle(request);
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* uWS: normalize a Harper response body into what the adapter can serialize. Finite bodies collapse
|
|
876
|
+
* to a string/Buffer; a Node stream or async-iterable is returned as a Readable so writeResponse can
|
|
877
|
+
* stream it incrementally (buffering an SSE/event-stream body here would never return). `signal`
|
|
878
|
+
* aborts the collapse of a sync iterable if the client disconnects mid-response.
|
|
879
|
+
*/
|
|
880
|
+
/**
|
|
881
|
+
* Drive a `send` SendStream to completion against a Writable shim, capturing the headers it writes
|
|
882
|
+
* (setHeader/writeHead) onto `headers` and the status it sets (statusCode/writeHead) and buffering
|
|
883
|
+
* the file body. `send` targets an http.ServerResponse (setHeader/writeHead/statusCode/finished);
|
|
884
|
+
* uWS has none, so we adapt — mirrors the Bun fetchHandler's SendStream path. The captured status
|
|
885
|
+
* carries send's conditional-GET (304) and Range (206/416) results. Buffering is fine for static
|
|
886
|
+
* assets and keeps Content-Length set. `defaultStatus` is used when send sets none.
|
|
887
|
+
*/
|
|
888
|
+
function bufferSendStream(body, headers, defaultStatus, signal) {
|
|
889
|
+
return new Promise((resolve, reject) => {
|
|
890
|
+
const chunks = [];
|
|
891
|
+
const dest = new node_stream_1.Writable({
|
|
892
|
+
write(chunk, _encoding, callback) {
|
|
893
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
894
|
+
callback();
|
|
895
|
+
},
|
|
896
|
+
final(callback) {
|
|
897
|
+
callback();
|
|
898
|
+
resolve({ body: Buffer.concat(chunks), status: dest.statusCode || defaultStatus });
|
|
899
|
+
},
|
|
900
|
+
});
|
|
901
|
+
Object.assign(dest, {
|
|
902
|
+
setHeader: (n, v) => headers.set(n, v),
|
|
903
|
+
getHeader: (n) => headers.get(n),
|
|
904
|
+
removeHeader: (n) => headers.delete(n.toLowerCase()),
|
|
905
|
+
// send conveys 304/206/416 via statusCode and/or writeHead's status arg — capture both so
|
|
906
|
+
// conditional-GET and Range responses aren't flattened to the default 200.
|
|
907
|
+
writeHead: (s, hdrs) => {
|
|
908
|
+
if (s)
|
|
909
|
+
dest.statusCode = s;
|
|
910
|
+
if (hdrs)
|
|
911
|
+
for (const k in hdrs)
|
|
912
|
+
headers.set(k, hdrs[k]);
|
|
913
|
+
},
|
|
914
|
+
statusCode: defaultStatus,
|
|
915
|
+
headersSent: false,
|
|
916
|
+
// 'on-finished' (used by 'send') treats a non-false `finished` as already-done and destroys
|
|
917
|
+
// the read stream before data flows; keep it false so it waits for the 'finish' event.
|
|
918
|
+
finished: false,
|
|
919
|
+
});
|
|
920
|
+
const onAbort = () => {
|
|
921
|
+
body.destroy?.();
|
|
922
|
+
dest.destroy?.();
|
|
923
|
+
reject(new Error('client aborted'));
|
|
924
|
+
};
|
|
925
|
+
if (signal) {
|
|
926
|
+
if (signal.aborted)
|
|
927
|
+
return onAbort();
|
|
928
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
929
|
+
}
|
|
930
|
+
body.on('error', reject);
|
|
931
|
+
dest.on('error', reject);
|
|
932
|
+
body.pipe(dest);
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
async function normalizeUwsBody(body, signal) {
|
|
936
|
+
if (body == null)
|
|
937
|
+
return null;
|
|
938
|
+
if (typeof body === 'string' || Buffer.isBuffer(body) || body instanceof Uint8Array)
|
|
939
|
+
return body;
|
|
940
|
+
if (body instanceof blob_ts_1.Blob)
|
|
941
|
+
return Buffer.from(await body.arrayBuffer());
|
|
942
|
+
if (typeof body.then === 'function')
|
|
943
|
+
return normalizeUwsBody(await body, signal);
|
|
944
|
+
// Already a Node stream — stream it as-is (re-wrapping in Readable.from breaks destroy propagation).
|
|
945
|
+
if (typeof body.pipe === 'function')
|
|
946
|
+
return body;
|
|
947
|
+
// Async-iterable (e.g. an event queue) — adapt to a Readable and stream it.
|
|
948
|
+
if (body[Symbol.asyncIterator])
|
|
949
|
+
return node_stream_1.Readable.from(body);
|
|
950
|
+
// Sync iterable — small/finite, collapse to a buffer.
|
|
951
|
+
if (body[Symbol.iterator]) {
|
|
952
|
+
const chunks = [];
|
|
953
|
+
for (const chunk of body) {
|
|
954
|
+
if (signal?.aborted)
|
|
955
|
+
throw new Error('client aborted');
|
|
956
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
957
|
+
}
|
|
958
|
+
return Buffer.concat(chunks);
|
|
959
|
+
}
|
|
960
|
+
return String(body);
|
|
961
|
+
}
|
|
669
962
|
/**
|
|
670
963
|
* Bun-specific HTTP server setup. Instead of creating a Node http.Server, we store a fetch handler config
|
|
671
964
|
* that will be passed to Bun.serve() when listenOnPorts() is called in threadServer.js.
|
|
@@ -696,14 +989,14 @@ function getBunHTTPServer(port, secure, options) {
|
|
|
696
989
|
response.headers.set(key, value);
|
|
697
990
|
}
|
|
698
991
|
if (response.status === -1) {
|
|
699
|
-
const fallbackServer =
|
|
992
|
+
const fallbackServer = fallbackServers[port];
|
|
700
993
|
if (fallbackServer) {
|
|
701
994
|
// Delegate to the fallback server (e.g. Fastify) via node:http compatibility.
|
|
702
995
|
// We create a Node-compatible IncomingMessage/ServerResponse and emit 'request'
|
|
703
996
|
// on the fallback server, then capture the response.
|
|
704
997
|
return await bunDelegateToNodeServer(fallbackServer, webRequest, request);
|
|
705
998
|
}
|
|
706
|
-
|
|
999
|
+
logHttpRequest(request, 404, requestId, performance.now() - startTime);
|
|
707
1000
|
return new Response('Not found\n', { status: 404 });
|
|
708
1001
|
}
|
|
709
1002
|
const status = response.status || 200;
|
|
@@ -758,7 +1051,7 @@ function getBunHTTPServer(port, secure, options) {
|
|
|
758
1051
|
(0, write_ts_1.recordAction)(executionTime, 'duration', handlerPath, method, response.wasCacheMiss == undefined ? undefined : response.wasCacheMiss ? 'cache-miss' : 'cache-hit');
|
|
759
1052
|
(0, write_ts_1.recordActionBinary)(status < 400, 'success', handlerPath, method);
|
|
760
1053
|
(0, write_ts_1.recordActionBinary)(1, 'response_' + status, handlerPath, method);
|
|
761
|
-
|
|
1054
|
+
logHttpRequest(request, status, requestId, executionTime);
|
|
762
1055
|
// Convert body to something Bun's Response can accept
|
|
763
1056
|
if (body instanceof ReadableStream) {
|
|
764
1057
|
return new Response(body, { status, headers: responseHeaders });
|
|
@@ -813,16 +1106,18 @@ function getBunHTTPServer(port, secure, options) {
|
|
|
813
1106
|
return new Response(body, { status, headers: responseHeaders });
|
|
814
1107
|
}
|
|
815
1108
|
catch (error) {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1109
|
+
// the HTTP status may be carried as `statusCode` (our error classes) or `status` (e.g. a thrown plain object)
|
|
1110
|
+
const statusCode = error.statusCode ?? error.status;
|
|
1111
|
+
const status = statusCode || 500;
|
|
1112
|
+
logHttpRequest(null, status, requestId, performance.now() - startTime);
|
|
1113
|
+
if (statusCode) {
|
|
1114
|
+
if (statusCode === 500)
|
|
1115
|
+
harper_logger_ts_1.default.warn(errorForLog(error));
|
|
821
1116
|
else
|
|
822
|
-
harper_logger_ts_1.default.info(error);
|
|
1117
|
+
harper_logger_ts_1.default.info(errorForLog(error));
|
|
823
1118
|
}
|
|
824
1119
|
else
|
|
825
|
-
harper_logger_ts_1.default.error(error);
|
|
1120
|
+
harper_logger_ts_1.default.error(errorForLog(error));
|
|
826
1121
|
return new Response(errorToString(error), { status });
|
|
827
1122
|
}
|
|
828
1123
|
};
|
|
@@ -858,42 +1153,53 @@ function getBunHTTPServer(port, secure, options) {
|
|
|
858
1153
|
* Bridge a Bun fetch request to a Node.js http.Server (e.g. Fastify) by using Fastify's inject()
|
|
859
1154
|
* method to send the request through its internal router without needing a real socket.
|
|
860
1155
|
*/
|
|
861
|
-
let
|
|
862
|
-
function
|
|
863
|
-
|
|
1156
|
+
let fastifyInstances = {};
|
|
1157
|
+
function registerFastifyInstance(port, instance) {
|
|
1158
|
+
fastifyInstances[port] = instance;
|
|
864
1159
|
}
|
|
865
1160
|
const INTERNAL_USER_HEADER = 'x-harper-internal-pre-auth-user';
|
|
1161
|
+
/**
|
|
1162
|
+
* Run a request through a Fastify instance via inject() — its internal router, no socket needed.
|
|
1163
|
+
* Shared by the Bun and uWS fallback-delegation paths. Strips any forged pre-auth header from the
|
|
1164
|
+
* client and, when Harper's auth middleware resolved a user without credentials (e.g. AUTHORIZE_LOCAL
|
|
1165
|
+
* for loopback in dev), forwards it so Fastify can skip its own auth — only when no Authorization
|
|
1166
|
+
* header was supplied, otherwise Fastify's Passport validates the credentials normally.
|
|
1167
|
+
* `payloadAsStream` makes inject() resolve as soon as the response headers are written and exposes
|
|
1168
|
+
* the body as a Readable, so a long-lived SSE response (the MCP server-push GET) streams instead of
|
|
1169
|
+
* buffering forever.
|
|
1170
|
+
*/
|
|
1171
|
+
function injectToFastify(fastify, req) {
|
|
1172
|
+
const headers = {};
|
|
1173
|
+
for (const key in req.headers) {
|
|
1174
|
+
if (key.toLowerCase() !== INTERNAL_USER_HEADER)
|
|
1175
|
+
headers[key] = req.headers[key];
|
|
1176
|
+
}
|
|
1177
|
+
// Both callers pass already-lowercased header keys (uWS lowercases at the protocol level →
|
|
1178
|
+
// RequestHeaders.asObject; Bun's webRequest.headers.forEach yields lowercase), so the literal
|
|
1179
|
+
// 'authorization' lookup is reliable — the pre-auth user is only forwarded when the client sent
|
|
1180
|
+
// no credentials of its own.
|
|
1181
|
+
if (req.user && !headers['authorization']) {
|
|
1182
|
+
headers[INTERNAL_USER_HEADER] = JSON.stringify(req.user);
|
|
1183
|
+
}
|
|
1184
|
+
return fastify.inject({ method: req.method, url: req.url, headers, payload: req.body, payloadAsStream: true });
|
|
1185
|
+
}
|
|
866
1186
|
async function bunDelegateToNodeServer(nodeServer, webRequest, bunRequest) {
|
|
867
1187
|
// Check if there's a Fastify instance registered for this port (preferred path)
|
|
868
|
-
for (const port in
|
|
869
|
-
if (
|
|
870
|
-
const fastify =
|
|
1188
|
+
for (const port in fallbackServers) {
|
|
1189
|
+
if (fallbackServers[port] === nodeServer && fastifyInstances[port]) {
|
|
1190
|
+
const fastify = fastifyInstances[port];
|
|
871
1191
|
const url = new URL(webRequest.url);
|
|
872
1192
|
const body = webRequest.body ? Buffer.from(await webRequest.arrayBuffer()) : undefined;
|
|
873
1193
|
const headers = {};
|
|
874
1194
|
webRequest.headers.forEach((value, key) => {
|
|
875
|
-
|
|
876
|
-
if (key.toLowerCase() !== INTERNAL_USER_HEADER)
|
|
877
|
-
headers[key] = value;
|
|
1195
|
+
headers[key] = value;
|
|
878
1196
|
});
|
|
879
|
-
|
|
880
|
-
// AUTHORIZE_LOCAL for loopback connections in dev mode), pass the user so Fastify can
|
|
881
|
-
// skip its own auth. Only applies when there is no Authorization header — if credentials
|
|
882
|
-
// were provided, let Fastify's Passport validate them normally.
|
|
883
|
-
if (bunRequest?.user && !headers['authorization']) {
|
|
884
|
-
headers[INTERNAL_USER_HEADER] = JSON.stringify(bunRequest.user);
|
|
885
|
-
}
|
|
886
|
-
// `payloadAsStream` makes inject() resolve as soon as the response headers are
|
|
887
|
-
// written and exposes the body as a Readable, instead of buffering the whole
|
|
888
|
-
// payload and resolving only on response end. Without it a long-lived SSE
|
|
889
|
-
// response (the MCP server-push GET) never ends, so `await inject()` would
|
|
890
|
-
// hang forever and the client would never receive headers.
|
|
891
|
-
const injectResult = await fastify.inject({
|
|
1197
|
+
const injectResult = await injectToFastify(fastify, {
|
|
892
1198
|
method: webRequest.method,
|
|
893
1199
|
url: url.pathname + url.search,
|
|
894
1200
|
headers,
|
|
895
|
-
|
|
896
|
-
|
|
1201
|
+
body,
|
|
1202
|
+
user: bunRequest?.user,
|
|
897
1203
|
});
|
|
898
1204
|
const webHeaders = new globalThis.Headers();
|
|
899
1205
|
for (const [k, v] of Object.entries(injectResult.headers)) {
|
|
@@ -940,14 +1246,68 @@ async function bunDelegateToNodeServer(nodeServer, webRequest, bunRequest) {
|
|
|
940
1246
|
// No Fastify instance found — return 404
|
|
941
1247
|
return new Response('Not found\n', { status: 404 });
|
|
942
1248
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1249
|
+
// Resolved order captured at chain-build time, keyed identically to httpChain/upgradeChains/
|
|
1250
|
+
// websocketChains (kind → port → routes). Reporting the stored build-time order rather than
|
|
1251
|
+
// recomputing from current responders guarantees get_status matches the callback chain actually
|
|
1252
|
+
// serving that port — including cases where a late `port: 'all'` registration rebuilds only the
|
|
1253
|
+
// 'all' chain and leaves a concrete port's chain (and this description) unchanged (#1573).
|
|
1254
|
+
const resolvedChainDescriptions = {
|
|
1255
|
+
http: {},
|
|
1256
|
+
upgrade: {},
|
|
1257
|
+
websocket: {},
|
|
1258
|
+
};
|
|
1259
|
+
function makeCallbackChain(responders, portNum, requestArgIndex = 0, kind = 'http') {
|
|
1260
|
+
const onCycle = () => {
|
|
1261
|
+
harper_logger_ts_1.default.warn(`Cycle detected in ${kind} middleware before/after ordering on port ${portNum}; falling back to registration order.`);
|
|
1262
|
+
};
|
|
1263
|
+
// describeChains reuses the same resolvers as buildCallbackChain, so this is the served order.
|
|
1264
|
+
// onCycle is omitted: the build call owns the single cycle warning, and on a cycle describeChains
|
|
1265
|
+
// falls back to registration order exactly as the built chain does.
|
|
1266
|
+
const routes = (0, middlewareChain_ts_1.describeChains)(responders, portNum).map((route) => ({
|
|
1267
|
+
host: route.host,
|
|
1268
|
+
urlPath: route.urlPath,
|
|
1269
|
+
order: route.order.map((entry) => entry.name ?? '(anonymous)'),
|
|
1270
|
+
}));
|
|
1271
|
+
resolvedChainDescriptions[kind][portNum] = routes;
|
|
1272
|
+
if (harper_logger_ts_1.default.debug) {
|
|
1273
|
+
for (const route of routes) {
|
|
1274
|
+
const scope = route.host || route.urlPath ? ` [${route.host ?? '*'}${route.urlPath ?? ''}]` : '';
|
|
1275
|
+
harper_logger_ts_1.default.debug(`Resolved ${kind} middleware chain on port ${portNum}${scope}: ${route.order.join(' → ') || '(empty)'}`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return (0, middlewareChain_ts_1.makeCallbackChain)(responders, portNum, unhandled, onCycle, requestArgIndex, ({ entryName, kind: refKind, target }) => {
|
|
1279
|
+
harper_logger_ts_1.default.warn(`Middleware ordering: ${entryName ? `'${entryName}'` : 'a handler'} requested \`${refKind}: '${target}'\` but no handler named '${target}' is registered on port ${portNum}, so the constraint is ignored. Handler names are the config keys as registered (e.g. 'rest').`);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Returns the resolved middleware order for every built HTTP, upgrade, and WebSocket chain on the
|
|
1284
|
+
* current thread, as plain serializable data (listeners omitted). Surfaced via the `get_status`
|
|
1285
|
+
* operation so chain placement can be verified on a running instance (#1573). The 'all' pseudo-port
|
|
1286
|
+
* is excluded: makeCallbackChain builds a chain for it, but it is not a bound listener (its
|
|
1287
|
+
* responders already fold into every concrete port's chain).
|
|
1288
|
+
*
|
|
1289
|
+
* Note: this reflects the calling thread's built chains. All HTTP worker threads register
|
|
1290
|
+
* identically, so any worker's view is representative.
|
|
1291
|
+
*/
|
|
1292
|
+
function describeMiddlewareChains() {
|
|
1293
|
+
const concretePorts = (byPort) => {
|
|
1294
|
+
const out = {};
|
|
1295
|
+
for (const port of Object.keys(byPort))
|
|
1296
|
+
if (port !== 'all')
|
|
1297
|
+
out[port] = byPort[port];
|
|
1298
|
+
return out;
|
|
1299
|
+
};
|
|
1300
|
+
return {
|
|
1301
|
+
http: concretePorts(resolvedChainDescriptions.http),
|
|
1302
|
+
upgrade: concretePorts(resolvedChainDescriptions.upgrade),
|
|
1303
|
+
websocket: concretePorts(resolvedChainDescriptions.websocket),
|
|
1304
|
+
};
|
|
947
1305
|
}
|
|
948
1306
|
function unhandled(request) {
|
|
949
|
-
if (request.user) {
|
|
950
|
-
// pass on authentication information to the next server
|
|
1307
|
+
if (request.user && request._nodeRequest) {
|
|
1308
|
+
// pass on authentication information to the next server (Node fallback delegation via the
|
|
1309
|
+
// 'unhandled' event chain). The Bun/uWS adapters have no _nodeRequest; they forward the
|
|
1310
|
+
// resolved user to the Fastify fallback via injectToFastify's INTERNAL_USER_HEADER instead.
|
|
951
1311
|
request._nodeRequest.user = request.user;
|
|
952
1312
|
}
|
|
953
1313
|
return {
|
|
@@ -982,7 +1342,7 @@ function onUpgrade(listener, options) {
|
|
|
982
1342
|
host: options?.host || undefined,
|
|
983
1343
|
};
|
|
984
1344
|
upgradeListeners[options?.runFirst ? 'unshift' : 'push'](entry);
|
|
985
|
-
upgradeChains[port] = makeCallbackChain(upgradeListeners, port);
|
|
1345
|
+
upgradeChains[port] = makeCallbackChain(upgradeListeners, port, 0, 'upgrade');
|
|
986
1346
|
}
|
|
987
1347
|
}
|
|
988
1348
|
const websocketListeners = [], websocketChains = {};
|
|
@@ -1000,7 +1360,39 @@ function onWebSocket(listener, options) {
|
|
|
1000
1360
|
name: (0, componentLoader_ts_1.getComponentName)(),
|
|
1001
1361
|
});
|
|
1002
1362
|
const server = getHTTPServer(port, secure, options);
|
|
1003
|
-
if (
|
|
1363
|
+
if (server?.uws) {
|
|
1364
|
+
// uWS-backed port (HARPER_UWS_HTTP): uWS owns the socket, so route upgrades through uWS's
|
|
1365
|
+
// native app.ws() rather than the Node ws.WebSocketServer + server 'upgrade' event. We wire a
|
|
1366
|
+
// wsHandler into the shared uwsServeConfig; createUwsServer registers app.ws() when it listens.
|
|
1367
|
+
const cfg = exports.uwsServeConfigs[port];
|
|
1368
|
+
if (cfg && !cfg.wsHandler) {
|
|
1369
|
+
// Honor a configured WebSocket maxPayload on the uWS transport too (else it defaults to 100 MiB).
|
|
1370
|
+
if (options.maxPayload != null)
|
|
1371
|
+
cfg.wsMaxPayload = options.maxPayload;
|
|
1372
|
+
cfg.wsHandler = (ws, upgrade) => {
|
|
1373
|
+
try {
|
|
1374
|
+
const request = new Request_ts_1.UwsRequest({
|
|
1375
|
+
method: 'GET',
|
|
1376
|
+
url: upgrade.url,
|
|
1377
|
+
headers: upgrade.headers,
|
|
1378
|
+
secure,
|
|
1379
|
+
ip: upgrade.ip,
|
|
1380
|
+
});
|
|
1381
|
+
request.isWebSocket = true;
|
|
1382
|
+
const chainCompletion = httpChain[port](request);
|
|
1383
|
+
websocketChains[port](ws, request, chainCompletion);
|
|
1384
|
+
}
|
|
1385
|
+
catch (error) {
|
|
1386
|
+
harper_logger_ts_1.default.warn('Error in handling WS connection', error);
|
|
1387
|
+
try {
|
|
1388
|
+
ws.close();
|
|
1389
|
+
}
|
|
1390
|
+
catch { }
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
else if (!websocketServers[port]) {
|
|
1004
1396
|
websocketServers[port] = new ws_1.WebSocketServer({
|
|
1005
1397
|
noServer: true,
|
|
1006
1398
|
// TODO: this should be a global config and not per ws listener
|
|
@@ -1050,7 +1442,7 @@ function onWebSocket(listener, options) {
|
|
|
1050
1442
|
host: options?.host || undefined,
|
|
1051
1443
|
};
|
|
1052
1444
|
websocketListeners[options?.runFirst ? 'unshift' : 'push'](wsEntry);
|
|
1053
|
-
websocketChains[port] = makeCallbackChain(websocketListeners, port, 1);
|
|
1445
|
+
websocketChains[port] = makeCallbackChain(websocketListeners, port, 1, 'websocket');
|
|
1054
1446
|
// mqtt doesn't invoke the http handler so this needs to be here to load up the http chains.
|
|
1055
1447
|
httpChain[port] = makeCallbackChain(httpResponders, port);
|
|
1056
1448
|
}
|
|
@@ -1128,6 +1520,97 @@ function enableProxyProtocol(httpServer) {
|
|
|
1128
1520
|
});
|
|
1129
1521
|
});
|
|
1130
1522
|
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Front a cleartext HTTP/2 server with optional PROXY v1 handling on a Unix domain socket.
|
|
1525
|
+
*
|
|
1526
|
+
* enableProxyProtocol() can't be used here: Node's Http2Session consumes the socket's
|
|
1527
|
+
* native handle directly, so data never surfaces as JS 'data' events to intercept. The
|
|
1528
|
+
* PROXY header must instead be consumed *before* the socket is handed to the HTTP/2
|
|
1529
|
+
* server. Bytes beyond the header (typically the coalesced h2 connection preface) are
|
|
1530
|
+
* unshifted back onto the socket; the native session picks them up (verified on Node 24
|
|
1531
|
+
* — covered by a unit test so a Node upgrade regressing this fails loudly).
|
|
1532
|
+
*
|
|
1533
|
+
* The returned server's close() also gracefully closes live h2 sessions (GOAWAY,
|
|
1534
|
+
* in-flight streams finish) so closeServers()'s generic server.close() drains instead
|
|
1535
|
+
* of riding its 5s force-exit backstop — the h1 mirror gets this via http.Server's
|
|
1536
|
+
* closeIdleConnections drain, which a net.Server doesn't have.
|
|
1537
|
+
*/
|
|
1538
|
+
function createH2CProxyFront(h2Server, prehandoffTimeout = 10_000) {
|
|
1539
|
+
const sessions = new Set();
|
|
1540
|
+
const prehandoffSockets = new Set();
|
|
1541
|
+
let closing = false;
|
|
1542
|
+
h2Server.on('session', (session) => {
|
|
1543
|
+
// A connection can be mid-handoff (header read, session not yet created) when
|
|
1544
|
+
// close() runs — its session forms after the close sweep, so close it here or
|
|
1545
|
+
// it would never receive GOAWAY and would ride the 5s force-exit backstop.
|
|
1546
|
+
if (closing)
|
|
1547
|
+
session.close();
|
|
1548
|
+
sessions.add(session);
|
|
1549
|
+
session.on('close', () => sessions.delete(session));
|
|
1550
|
+
});
|
|
1551
|
+
const front = (0, node_net_2.createServer)({ noDelay: true }, (socket) => {
|
|
1552
|
+
let buf = null;
|
|
1553
|
+
// Until handoff the h2 session's own handlers aren't attached yet: swallow socket
|
|
1554
|
+
// errors (a reset mid-header) and bound how long we'll wait for the header, so a
|
|
1555
|
+
// stalled connection can't hold an fd forever.
|
|
1556
|
+
prehandoffSockets.add(socket);
|
|
1557
|
+
socket.on('close', () => prehandoffSockets.delete(socket));
|
|
1558
|
+
const onPrehandoffError = () => socket.destroy();
|
|
1559
|
+
socket.on('error', onPrehandoffError);
|
|
1560
|
+
const onPrehandoffTimeout = () => socket.destroy();
|
|
1561
|
+
socket.setTimeout(prehandoffTimeout, onPrehandoffTimeout);
|
|
1562
|
+
const handoff = (rest) => {
|
|
1563
|
+
prehandoffSockets.delete(socket);
|
|
1564
|
+
socket.removeListener('readable', onReadable);
|
|
1565
|
+
socket.removeListener('error', onPrehandoffError);
|
|
1566
|
+
socket.setTimeout(0);
|
|
1567
|
+
socket.removeListener('timeout', onPrehandoffTimeout);
|
|
1568
|
+
if (rest.length > 0)
|
|
1569
|
+
socket.unshift(rest);
|
|
1570
|
+
h2Server.emit('connection', socket);
|
|
1571
|
+
};
|
|
1572
|
+
const onReadable = () => {
|
|
1573
|
+
let chunk;
|
|
1574
|
+
while ((chunk = socket.read()) !== null) {
|
|
1575
|
+
buf = buf ? Buffer.concat([buf, chunk]) : chunk;
|
|
1576
|
+
// Compare against "PROXY " for as many bytes as we have so far; a non-PROXY
|
|
1577
|
+
// prefix (e.g. a direct h2 client with no fronting proxy) is handed off as-is.
|
|
1578
|
+
const cmpLen = Math.min(PROXY_V1_PREFIX.length, buf.length);
|
|
1579
|
+
if (buf.compare(PROXY_V1_PREFIX, 0, cmpLen, 0, cmpLen) !== 0)
|
|
1580
|
+
return handoff(buf);
|
|
1581
|
+
const eol = buf.indexOf('\r\n');
|
|
1582
|
+
if (eol !== -1) {
|
|
1583
|
+
// Complete header: "PROXY TCP4 <src-ip> <dst-ip> <src-port> <dst-port>"
|
|
1584
|
+
const parts = buf.toString('latin1', 0, eol).split(' ');
|
|
1585
|
+
if (parts.length === 6) {
|
|
1586
|
+
// Override the UDS socket's undefined remoteAddress/remotePort with the real
|
|
1587
|
+
// client values; http2's compat req.socket proxies through to these.
|
|
1588
|
+
Object.defineProperty(socket, 'remoteAddress', { value: parts[2], configurable: true });
|
|
1589
|
+
Object.defineProperty(socket, 'remotePort', { value: parseInt(parts[4], 10), configurable: true });
|
|
1590
|
+
}
|
|
1591
|
+
return handoff(buf.subarray(eol + 2));
|
|
1592
|
+
}
|
|
1593
|
+
// No CRLF within the spec max — not a valid PROXY header after all.
|
|
1594
|
+
if (buf.length >= PROXY_V1_MAX_HEADER)
|
|
1595
|
+
return handoff(buf);
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
socket.on('readable', onReadable);
|
|
1599
|
+
});
|
|
1600
|
+
const netClose = front.close.bind(front);
|
|
1601
|
+
front.close = (callback) => {
|
|
1602
|
+
closing = true;
|
|
1603
|
+
for (const session of sessions)
|
|
1604
|
+
session.close();
|
|
1605
|
+
// Header-waiting sockets carry no in-flight work; drop them so they can't hold
|
|
1606
|
+
// the close callback open for the rest of the pre-handoff timeout.
|
|
1607
|
+
for (const socket of prehandoffSockets)
|
|
1608
|
+
socket.destroy();
|
|
1609
|
+
h2Server.close();
|
|
1610
|
+
return netClose(callback);
|
|
1611
|
+
};
|
|
1612
|
+
return front;
|
|
1613
|
+
}
|
|
1131
1614
|
function defaultNotFound(request, response) {
|
|
1132
1615
|
if (response.headersSent || response.writableEnded)
|
|
1133
1616
|
return;
|
|
@@ -1136,7 +1619,7 @@ function defaultNotFound(request, response) {
|
|
|
1136
1619
|
logRequest(request, 404, 0, request.requestId);
|
|
1137
1620
|
}
|
|
1138
1621
|
let httpLogger;
|
|
1139
|
-
function
|
|
1622
|
+
function logHttpRequest(request, status, requestId, executionTime) {
|
|
1140
1623
|
const logging = httpOptions.logging;
|
|
1141
1624
|
if (logging) {
|
|
1142
1625
|
if (!httpLogger) {
|