@morojs/moro 1.7.30 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -46
- package/dist/core/auth/morojs-adapter.js +95 -45
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/config/config-manager.js.map +1 -1
- package/dist/core/config/config-sources.js +119 -48
- package/dist/core/config/config-sources.js.map +1 -1
- package/dist/core/config/config-validator.js +189 -8
- package/dist/core/config/config-validator.js.map +1 -1
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.js +35 -4
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/database/adapters/drizzle.js +2 -2
- package/dist/core/database/adapters/drizzle.js.map +1 -1
- package/dist/core/database/adapters/index.js.map +1 -1
- package/dist/core/database/adapters/mongodb.d.ts +1 -0
- package/dist/core/database/adapters/mongodb.js +16 -0
- package/dist/core/database/adapters/mongodb.js.map +1 -1
- package/dist/core/database/adapters/mysql.d.ts +1 -0
- package/dist/core/database/adapters/mysql.js +13 -2
- package/dist/core/database/adapters/mysql.js.map +1 -1
- package/dist/core/database/adapters/postgresql.js +2 -2
- package/dist/core/database/adapters/postgresql.js.map +1 -1
- package/dist/core/database/adapters/redis.js.map +1 -1
- package/dist/core/database/adapters/sql-safety.js +5 -4
- package/dist/core/database/adapters/sql-safety.js.map +1 -1
- package/dist/core/database/adapters/sqlite.js.map +1 -1
- package/dist/core/database/index.js.map +1 -1
- package/dist/core/docs/index.js +11 -7
- package/dist/core/docs/index.js.map +1 -1
- package/dist/core/docs/openapi-generator.js +8 -6
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/schema-to-openapi.js +3 -3
- package/dist/core/docs/schema-to-openapi.js.map +1 -1
- package/dist/core/docs/simple-docs.js.map +1 -1
- package/dist/core/docs/swagger-ui.js.map +1 -1
- package/dist/core/docs/zod-to-openapi.js +2 -2
- package/dist/core/docs/zod-to-openapi.js.map +1 -1
- package/dist/core/events/event-bus.js +14 -5
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/events/index.js.map +1 -1
- package/dist/core/framework.d.ts +41 -3
- package/dist/core/framework.js +342 -95
- package/dist/core/framework.js.map +1 -1
- package/dist/core/graphql/adapter.js.map +1 -1
- package/dist/core/graphql/adapters/graphql-js-adapter.js.map +1 -1
- package/dist/core/graphql/core.js.map +1 -1
- package/dist/core/graphql/index.js.map +1 -1
- package/dist/core/graphql/loader.js.map +1 -1
- package/dist/core/graphql/types.js.map +1 -1
- package/dist/core/grpc/adapters/grpc-js-adapter.js +4 -0
- package/dist/core/grpc/adapters/grpc-js-adapter.js.map +1 -1
- package/dist/core/grpc/adapters/index.js.map +1 -1
- package/dist/core/grpc/grpc-adapter.js.map +1 -1
- package/dist/core/grpc/grpc-manager.js.map +1 -1
- package/dist/core/grpc/index.js.map +1 -1
- package/dist/core/grpc/middleware/auth.js +1 -1
- package/dist/core/grpc/middleware/auth.js.map +1 -1
- package/dist/core/grpc/middleware/logging.js.map +1 -1
- package/dist/core/grpc/middleware/validation.js +1 -1
- package/dist/core/grpc/middleware/validation.js.map +1 -1
- package/dist/core/grpc/types.d.ts +7 -7
- package/dist/core/grpc/types.js.map +1 -1
- package/dist/core/http/http-server.d.ts +17 -23
- package/dist/core/http/http-server.js +109 -249
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/http2-server.d.ts +6 -16
- package/dist/core/http/http2-server.js +40 -83
- package/dist/core/http/http2-server.js.map +1 -1
- package/dist/core/http/index.d.ts +1 -0
- package/dist/core/http/index.js +3 -0
- package/dist/core/http/index.js.map +1 -1
- package/dist/core/http/moro-engine-server.d.ts +263 -0
- package/dist/core/http/moro-engine-server.js +1962 -0
- package/dist/core/http/moro-engine-server.js.map +1 -0
- package/dist/core/http/utils/compression.d.ts +47 -0
- package/dist/core/http/utils/compression.js +132 -0
- package/dist/core/http/utils/compression.js.map +1 -0
- package/dist/core/http/utils/lazy-event-emitter.d.ts +11 -0
- package/dist/core/http/utils/lazy-event-emitter.js +69 -0
- package/dist/core/http/utils/lazy-event-emitter.js.map +1 -0
- package/dist/core/http/utils/multipart-parser.d.ts +32 -0
- package/dist/core/http/utils/multipart-parser.js +215 -0
- package/dist/core/http/utils/multipart-parser.js.map +1 -0
- package/dist/core/http/utils/query-parser.d.ts +1 -0
- package/dist/core/http/utils/query-parser.js +44 -0
- package/dist/core/http/utils/query-parser.js.map +1 -0
- package/dist/core/http/utils/size.d.ts +33 -0
- package/dist/core/http/utils/size.js +37 -0
- package/dist/core/http/utils/size.js.map +1 -0
- package/dist/core/http/utils/ssl-config.d.ts +69 -0
- package/dist/core/http/utils/ssl-config.js +116 -0
- package/dist/core/http/utils/ssl-config.js.map +1 -0
- package/dist/core/http/utils/uws-worker-clustering.js +45 -32
- package/dist/core/http/utils/uws-worker-clustering.js.map +1 -1
- package/dist/core/http/uws-http-server.d.ts +25 -3
- package/dist/core/http/uws-http-server.js +465 -86
- package/dist/core/http/uws-http-server.js.map +1 -1
- package/dist/core/jobs/cron-parser.js +7 -5
- package/dist/core/jobs/cron-parser.js.map +1 -1
- package/dist/core/jobs/index.js.map +1 -1
- package/dist/core/jobs/job-executor.d.ts +1 -1
- package/dist/core/jobs/job-executor.js.map +1 -1
- package/dist/core/jobs/job-scheduler.d.ts +3 -3
- package/dist/core/jobs/job-scheduler.js.map +1 -1
- package/dist/core/jobs/job-state-manager.d.ts +3 -3
- package/dist/core/jobs/job-state-manager.js +25 -4
- package/dist/core/jobs/job-state-manager.js.map +1 -1
- package/dist/core/jobs/leader-election.d.ts +15 -0
- package/dist/core/jobs/leader-election.js +158 -69
- package/dist/core/jobs/leader-election.js.map +1 -1
- package/dist/core/jobs/types.d.ts +4 -4
- package/dist/core/jobs/types.js.map +1 -1
- package/dist/core/jobs/utils.d.ts +1 -1
- package/dist/core/jobs/utils.js +7 -3
- package/dist/core/jobs/utils.js.map +1 -1
- package/dist/core/logger/filters.js +3 -2
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/index.js.map +1 -1
- package/dist/core/logger/logger.js +56 -25
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/logger/outputs.js.map +1 -1
- package/dist/core/mail/adapters/console-adapter.js.map +1 -1
- package/dist/core/mail/adapters/index.js.map +1 -1
- package/dist/core/mail/adapters/nodemailer-adapter.js.map +1 -1
- package/dist/core/mail/adapters/resend-adapter.js.map +1 -1
- package/dist/core/mail/adapters/sendgrid-adapter.js.map +1 -1
- package/dist/core/mail/adapters/ses-adapter.js.map +1 -1
- package/dist/core/mail/index.js.map +1 -1
- package/dist/core/mail/mail-adapter.js.map +1 -1
- package/dist/core/mail/mail-manager.d.ts +10 -0
- package/dist/core/mail/mail-manager.js +21 -5
- package/dist/core/mail/mail-manager.js.map +1 -1
- package/dist/core/mail/template-engine.js.map +1 -1
- package/dist/core/mail/types.js.map +1 -1
- package/dist/core/middleware/built-in/auth/core.js +3 -3
- package/dist/core/middleware/built-in/auth/core.js.map +1 -1
- package/dist/core/middleware/built-in/auth/helpers.js +4 -4
- package/dist/core/middleware/built-in/auth/helpers.js.map +1 -1
- package/dist/core/middleware/built-in/auth/hook.js.map +1 -1
- package/dist/core/middleware/built-in/auth/index.js.map +1 -1
- package/dist/core/middleware/built-in/auth/jwt-helpers.d.ts +4 -4
- package/dist/core/middleware/built-in/auth/jwt-helpers.js.map +1 -1
- package/dist/core/middleware/built-in/auth/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/auth/providers.js.map +1 -1
- package/dist/core/middleware/built-in/body-parsers/index.js.map +1 -1
- package/dist/core/middleware/built-in/body-size/core.js +2 -2
- package/dist/core/middleware/built-in/body-size/core.js.map +1 -1
- package/dist/core/middleware/built-in/body-size/hook.js.map +1 -1
- package/dist/core/middleware/built-in/body-size/index.js.map +1 -1
- package/dist/core/middleware/built-in/body-size/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/file.js +3 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/file.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/index.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/memory.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/cache/redis.js.map +1 -1
- package/dist/core/middleware/built-in/cache/adapters/index.js.map +1 -1
- package/dist/core/middleware/built-in/cache/core.js.map +1 -1
- package/dist/core/middleware/built-in/cache/hook.js +3 -3
- package/dist/core/middleware/built-in/cache/hook.js.map +1 -1
- package/dist/core/middleware/built-in/cache/index.js.map +1 -1
- package/dist/core/middleware/built-in/cache/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/azure.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudflare.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/cloudfront.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/cdn/index.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/adapters/index.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/core.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/hook.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/index.js.map +1 -1
- package/dist/core/middleware/built-in/cdn/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/compression/core.d.ts +3 -0
- package/dist/core/middleware/built-in/compression/core.js +28 -34
- package/dist/core/middleware/built-in/compression/core.js.map +1 -1
- package/dist/core/middleware/built-in/compression/hook.js.map +1 -1
- package/dist/core/middleware/built-in/compression/index.js.map +1 -1
- package/dist/core/middleware/built-in/compression/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/cookie/core.js.map +1 -1
- package/dist/core/middleware/built-in/cookie/hook.js.map +1 -1
- package/dist/core/middleware/built-in/cookie/index.js.map +1 -1
- package/dist/core/middleware/built-in/cookie/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/cors/core.js.map +1 -1
- package/dist/core/middleware/built-in/cors/hook.js.map +1 -1
- package/dist/core/middleware/built-in/cors/index.js.map +1 -1
- package/dist/core/middleware/built-in/cors/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/csp/core.js.map +1 -1
- package/dist/core/middleware/built-in/csp/hook.js.map +1 -1
- package/dist/core/middleware/built-in/csp/index.js.map +1 -1
- package/dist/core/middleware/built-in/csp/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/csrf/core.js +1 -1
- package/dist/core/middleware/built-in/csrf/core.js.map +1 -1
- package/dist/core/middleware/built-in/csrf/hook.js.map +1 -1
- package/dist/core/middleware/built-in/csrf/index.js.map +1 -1
- package/dist/core/middleware/built-in/csrf/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/error-tracker/index.js.map +1 -1
- package/dist/core/middleware/built-in/error-tracker/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/graphql/core.js.map +1 -1
- package/dist/core/middleware/built-in/graphql/helpers.js.map +1 -1
- package/dist/core/middleware/built-in/graphql/hook.js.map +1 -1
- package/dist/core/middleware/built-in/graphql/index.js.map +1 -1
- package/dist/core/middleware/built-in/graphql/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/graphql/subscriptions.js.map +1 -1
- package/dist/core/middleware/built-in/helmet/core.js.map +1 -1
- package/dist/core/middleware/built-in/helmet/hook.js.map +1 -1
- package/dist/core/middleware/built-in/helmet/index.js.map +1 -1
- package/dist/core/middleware/built-in/helmet/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/http2/core.js.map +1 -1
- package/dist/core/middleware/built-in/http2/hook.js.map +1 -1
- package/dist/core/middleware/built-in/http2/index.js.map +1 -1
- package/dist/core/middleware/built-in/http2/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/index.js.map +1 -1
- package/dist/core/middleware/built-in/performance-monitor/index.js.map +1 -1
- package/dist/core/middleware/built-in/performance-monitor/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/range/core.js +4 -1
- package/dist/core/middleware/built-in/range/core.js.map +1 -1
- package/dist/core/middleware/built-in/range/hook.js.map +1 -1
- package/dist/core/middleware/built-in/range/index.js.map +1 -1
- package/dist/core/middleware/built-in/range/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/rate-limit/core.js.map +1 -1
- package/dist/core/middleware/built-in/rate-limit/hook.js.map +1 -1
- package/dist/core/middleware/built-in/rate-limit/index.js.map +1 -1
- package/dist/core/middleware/built-in/rate-limit/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/request-logger/index.js.map +1 -1
- package/dist/core/middleware/built-in/request-logger/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/session/core.js +4 -4
- package/dist/core/middleware/built-in/session/core.js.map +1 -1
- package/dist/core/middleware/built-in/session/hook.js.map +1 -1
- package/dist/core/middleware/built-in/session/index.js.map +1 -1
- package/dist/core/middleware/built-in/session/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/sse/core.js.map +1 -1
- package/dist/core/middleware/built-in/sse/hook.js.map +1 -1
- package/dist/core/middleware/built-in/sse/index.js.map +1 -1
- package/dist/core/middleware/built-in/sse/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/static/core.js.map +1 -1
- package/dist/core/middleware/built-in/static/hook.js.map +1 -1
- package/dist/core/middleware/built-in/static/index.js.map +1 -1
- package/dist/core/middleware/built-in/static/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/template/core.js +1 -1
- package/dist/core/middleware/built-in/template/core.js.map +1 -1
- package/dist/core/middleware/built-in/template/hook.js.map +1 -1
- package/dist/core/middleware/built-in/template/index.js.map +1 -1
- package/dist/core/middleware/built-in/template/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/upload/core.js.map +1 -1
- package/dist/core/middleware/built-in/upload/hook.js.map +1 -1
- package/dist/core/middleware/built-in/upload/index.js.map +1 -1
- package/dist/core/middleware/built-in/upload/middleware.js.map +1 -1
- package/dist/core/middleware/built-in/validation/core.js.map +1 -1
- package/dist/core/middleware/built-in/validation/hook.js.map +1 -1
- package/dist/core/middleware/built-in/validation/index.js.map +1 -1
- package/dist/core/middleware/built-in/validation/middleware.js.map +1 -1
- package/dist/core/middleware/index.js +2 -2
- package/dist/core/middleware/index.js.map +1 -1
- package/dist/core/modules/auto-discovery.js +11 -7
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/index.js.map +1 -1
- package/dist/core/modules/modules.js +18 -13
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/adapters/engine-adapter.d.ts +23 -0
- package/dist/core/networking/adapters/engine-adapter.js +365 -0
- package/dist/core/networking/adapters/engine-adapter.js.map +1 -0
- package/dist/core/networking/adapters/index.d.ts +1 -0
- package/dist/core/networking/adapters/index.js +1 -0
- package/dist/core/networking/adapters/index.js.map +1 -1
- package/dist/core/networking/adapters/socketio-adapter.d.ts +1 -0
- package/dist/core/networking/adapters/socketio-adapter.js +12 -1
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -1
- package/dist/core/networking/adapters/uws-adapter.js +27 -16
- package/dist/core/networking/adapters/uws-adapter.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.d.ts +15 -0
- package/dist/core/networking/adapters/ws-adapter.js +120 -6
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/index.js.map +1 -1
- package/dist/core/networking/service-discovery.js +7 -5
- package/dist/core/networking/service-discovery.js.map +1 -1
- package/dist/core/networking/websocket-adapter.d.ts +22 -3
- package/dist/core/networking/websocket-adapter.js.map +1 -1
- package/dist/core/networking/websocket-manager.js +70 -67
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/pooling/object-pool-manager.js +6 -0
- package/dist/core/pooling/object-pool-manager.js.map +1 -1
- package/dist/core/queue/adapters/bull-adapter.js.map +1 -1
- package/dist/core/queue/adapters/index.js.map +1 -1
- package/dist/core/queue/adapters/kafka-adapter.js +2 -2
- package/dist/core/queue/adapters/kafka-adapter.js.map +1 -1
- package/dist/core/queue/adapters/memory-adapter.js +6 -6
- package/dist/core/queue/adapters/memory-adapter.js.map +1 -1
- package/dist/core/queue/adapters/rabbitmq-adapter.d.ts +50 -0
- package/dist/core/queue/adapters/rabbitmq-adapter.js +176 -20
- package/dist/core/queue/adapters/rabbitmq-adapter.js.map +1 -1
- package/dist/core/queue/adapters/sqs-adapter.d.ts +12 -2
- package/dist/core/queue/adapters/sqs-adapter.js +75 -24
- package/dist/core/queue/adapters/sqs-adapter.js.map +1 -1
- package/dist/core/queue/index.js.map +1 -1
- package/dist/core/queue/middleware/index.js.map +1 -1
- package/dist/core/queue/middleware/monitoring.d.ts +1 -1
- package/dist/core/queue/middleware/monitoring.js.map +1 -1
- package/dist/core/queue/middleware/priority.js +2 -1
- package/dist/core/queue/middleware/priority.js.map +1 -1
- package/dist/core/queue/middleware/rate-limit.js.map +1 -1
- package/dist/core/queue/queue-adapter.js.map +1 -1
- package/dist/core/queue/queue-manager.js.map +1 -1
- package/dist/core/queue/types.d.ts +8 -8
- package/dist/core/queue/types.js.map +1 -1
- package/dist/core/routing/app-integration.js.map +1 -1
- package/dist/core/routing/create-router.js.map +1 -1
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/routing/path-matcher.js.map +1 -1
- package/dist/core/routing/radix-tree.js.map +1 -1
- package/dist/core/routing/router.js.map +1 -1
- package/dist/core/routing/unified-router.js +12 -4
- package/dist/core/routing/unified-router.js.map +1 -1
- package/dist/core/runtime/aws-lambda-adapter.js.map +1 -1
- package/dist/core/runtime/base-adapter.js +2 -2
- package/dist/core/runtime/base-adapter.js.map +1 -1
- package/dist/core/runtime/cloudflare-workers-adapter.js +2 -0
- package/dist/core/runtime/cloudflare-workers-adapter.js.map +1 -1
- package/dist/core/runtime/index.js.map +1 -1
- package/dist/core/runtime/node-adapter.js +19 -17
- package/dist/core/runtime/node-adapter.js.map +1 -1
- package/dist/core/runtime/vercel-edge-adapter.js +2 -0
- package/dist/core/runtime/vercel-edge-adapter.js.map +1 -1
- package/dist/core/utilities/circuit-breaker.d.ts +1 -0
- package/dist/core/utilities/circuit-breaker.js +25 -2
- package/dist/core/utilities/circuit-breaker.js.map +1 -1
- package/dist/core/utilities/container.js +9 -9
- package/dist/core/utilities/container.js.map +1 -1
- package/dist/core/utilities/hooks.js +28 -6
- package/dist/core/utilities/hooks.js.map +1 -1
- package/dist/core/utilities/index.js.map +1 -1
- package/dist/core/utilities/module-path.js.map +1 -1
- package/dist/core/utilities/package-utils.d.ts +68 -0
- package/dist/core/utilities/package-utils.js +198 -1
- package/dist/core/utilities/package-utils.js.map +1 -1
- package/dist/core/utilities/response-helpers.js +1 -1
- package/dist/core/utilities/response-helpers.js.map +1 -1
- package/dist/core/validation/adapters.js.map +1 -1
- package/dist/core/validation/error-handler.js.map +1 -1
- package/dist/core/validation/index.js.map +1 -1
- package/dist/core/validation/schema-interface.js.map +1 -1
- package/dist/core/workers/facade.js.map +1 -1
- package/dist/core/workers/index.js.map +1 -1
- package/dist/core/workers/worker-entry.d.ts +1 -0
- package/dist/core/workers/worker-entry.js +12 -0
- package/dist/core/workers/worker-entry.js.map +1 -0
- package/dist/core/workers/worker-manager.d.ts +23 -2
- package/dist/core/workers/worker-manager.js +117 -17
- package/dist/core/workers/worker-manager.js.map +1 -1
- package/dist/core/workers/worker.js +8 -6
- package/dist/core/workers/worker.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +20 -1
- package/dist/moro.js +230 -70
- package/dist/moro.js.map +1 -1
- package/dist/types/auth.d.ts +4 -4
- package/dist/types/auth.js.map +1 -1
- package/dist/types/cache.js.map +1 -1
- package/dist/types/cdn.js.map +1 -1
- package/dist/types/config.d.ts +100 -15
- package/dist/types/config.js.map +1 -1
- package/dist/types/core.js.map +1 -1
- package/dist/types/database.js.map +1 -1
- package/dist/types/discovery.js.map +1 -1
- package/dist/types/events.js.map +1 -1
- package/dist/types/hooks.js.map +1 -1
- package/dist/types/http.js.map +1 -1
- package/dist/types/logger.js.map +1 -1
- package/dist/types/module.js.map +1 -1
- package/dist/types/runtime.js.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/package.json +32 -17
|
@@ -3,27 +3,13 @@
|
|
|
3
3
|
import cluster from 'cluster';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
5
|
import { createFrameworkLogger } from '../logger/index.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const eqIdx = pair.indexOf('=');
|
|
14
|
-
if (eqIdx > 0) {
|
|
15
|
-
const key = pair.substring(0, eqIdx);
|
|
16
|
-
const value = pair.substring(eqIdx + 1);
|
|
17
|
-
try {
|
|
18
|
-
result[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
result[key] = value;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return result;
|
|
26
|
-
}
|
|
6
|
+
import { loadNativeEngine, getNativeEngineLoadErrors } from '../utilities/package-utils.js';
|
|
7
|
+
import { parseMultipart as parseMultipartBuffer, } from './utils/multipart-parser.js';
|
|
8
|
+
import { LazyEventEmitter } from './utils/lazy-event-emitter.js';
|
|
9
|
+
import { parseRawQueryString } from './utils/query-parser.js';
|
|
10
|
+
import { resolveCompressionSettings, compressBuffer, isCompressible, negotiateEncoding, } from './utils/compression.js';
|
|
11
|
+
// Back-compat alias (this parser is now the transport-neutral one in utils/)
|
|
12
|
+
const parseUwsQueryString = parseRawQueryString;
|
|
27
13
|
/**
|
|
28
14
|
* Prototype-based request object for the uWS adapter.
|
|
29
15
|
*
|
|
@@ -33,7 +19,7 @@ function parseUwsQueryString(queryString) {
|
|
|
33
19
|
* so `materialize()` MUST be called before any `await` - it snapshots the
|
|
34
20
|
* headers and detaches the native reference.
|
|
35
21
|
*/
|
|
36
|
-
export class UwsRequest {
|
|
22
|
+
export class UwsRequest extends LazyEventEmitter {
|
|
37
23
|
method;
|
|
38
24
|
path;
|
|
39
25
|
url;
|
|
@@ -48,7 +34,9 @@ export class UwsRequest {
|
|
|
48
34
|
_cookies = undefined;
|
|
49
35
|
_context = undefined;
|
|
50
36
|
_requestId = undefined;
|
|
37
|
+
_socket = undefined;
|
|
51
38
|
constructor(server, uwsReq, method, url, queryString) {
|
|
39
|
+
super();
|
|
52
40
|
this._server = server;
|
|
53
41
|
this._uwsReq = uwsReq;
|
|
54
42
|
this.method = method;
|
|
@@ -56,6 +44,35 @@ export class UwsRequest {
|
|
|
56
44
|
this.url = url;
|
|
57
45
|
this._queryString = queryString || null;
|
|
58
46
|
}
|
|
47
|
+
// net.Socket-shaped shim so Node-style middleware (CSRF's req.socket.encrypted,
|
|
48
|
+
// rate-limiters' req.connection.remoteAddress) doesn't throw on this transport.
|
|
49
|
+
// uWS terminates plaintext HTTP here (TLS via its own listen options or a
|
|
50
|
+
// proxy); scheme detection should use x-forwarded-proto.
|
|
51
|
+
get socket() {
|
|
52
|
+
let s = this._socket;
|
|
53
|
+
if (s === undefined) {
|
|
54
|
+
const getIp = () => this.ip; // arrow captures this lexically (no this-alias)
|
|
55
|
+
s = {
|
|
56
|
+
get remoteAddress() {
|
|
57
|
+
return getIp() || undefined;
|
|
58
|
+
},
|
|
59
|
+
remotePort: undefined,
|
|
60
|
+
encrypted: false,
|
|
61
|
+
destroyed: false,
|
|
62
|
+
};
|
|
63
|
+
this._socket = s;
|
|
64
|
+
}
|
|
65
|
+
return s;
|
|
66
|
+
}
|
|
67
|
+
set socket(value) {
|
|
68
|
+
this._socket = value;
|
|
69
|
+
}
|
|
70
|
+
get connection() {
|
|
71
|
+
return this.socket;
|
|
72
|
+
}
|
|
73
|
+
set connection(value) {
|
|
74
|
+
this._socket = value;
|
|
75
|
+
}
|
|
59
76
|
/** Snapshot uWS-backed data and detach the native request. Idempotent.
|
|
60
77
|
* Must be called before returning control to the event loop if the
|
|
61
78
|
* request object outlives the synchronous route callback. */
|
|
@@ -109,6 +126,8 @@ export class UwsRequest {
|
|
|
109
126
|
const parts = header.split(';');
|
|
110
127
|
for (let i = 0; i < parts.length; i++) {
|
|
111
128
|
const part = parts[i];
|
|
129
|
+
if (part === undefined)
|
|
130
|
+
continue;
|
|
112
131
|
const eq = part.indexOf('=');
|
|
113
132
|
if (eq > 0) {
|
|
114
133
|
const name = part.substring(0, eq).trim();
|
|
@@ -157,12 +176,12 @@ export class UwsRequest {
|
|
|
157
176
|
// ==== Express-compatible request helpers (parity with the Node adapter) ====
|
|
158
177
|
get hostname() {
|
|
159
178
|
const host = this.headers.host || '';
|
|
160
|
-
return host ? host.split(':')[0] : '';
|
|
179
|
+
return host ? (host.split(':')[0] ?? '') : '';
|
|
161
180
|
}
|
|
162
181
|
get protocol() {
|
|
163
182
|
const forwardedProto = this.headers['x-forwarded-proto'];
|
|
164
183
|
if (forwardedProto)
|
|
165
|
-
return forwardedProto.split(',')[0].trim();
|
|
184
|
+
return (forwardedProto.split(',')[0] ?? '').trim();
|
|
166
185
|
return this._server && this._server.isSsl ? 'https' : 'http';
|
|
167
186
|
}
|
|
168
187
|
get secure() {
|
|
@@ -203,7 +222,7 @@ export class UwsRequest {
|
|
|
203
222
|
const ct = this.headers['content-type'] || '';
|
|
204
223
|
if (!ct)
|
|
205
224
|
return false;
|
|
206
|
-
const mime = ct.split(';')[0].trim().toLowerCase();
|
|
225
|
+
const mime = (ct.split(';')[0] ?? '').trim().toLowerCase();
|
|
207
226
|
const t = type.toLowerCase();
|
|
208
227
|
if (t.indexOf('/') === -1) {
|
|
209
228
|
return mime.endsWith(`/${t}`) || mime.endsWith(`+${t}`);
|
|
@@ -220,7 +239,7 @@ export class UwsRequest {
|
|
|
220
239
|
const wanted = Array.isArray(types) ? types : [types];
|
|
221
240
|
if (accept === '*/*' || accept === '')
|
|
222
241
|
return wanted[0] || false;
|
|
223
|
-
const acceptTypes = accept.split(',').map(s => s.split(';')[0].trim().toLowerCase());
|
|
242
|
+
const acceptTypes = accept.split(',').map(s => (s.split(';')[0] ?? '').trim().toLowerCase());
|
|
224
243
|
for (const w of wanted) {
|
|
225
244
|
const wl = w.toLowerCase();
|
|
226
245
|
const wMime = wl.indexOf('/') === -1 ? `application/${wl}` : wl;
|
|
@@ -246,7 +265,7 @@ export class UwsRequest {
|
|
|
246
265
|
.split(',')
|
|
247
266
|
.map(s => {
|
|
248
267
|
const parts = s.trim().split(';');
|
|
249
|
-
const tag = parts[0].toLowerCase();
|
|
268
|
+
const tag = (parts[0] ?? '').toLowerCase();
|
|
250
269
|
const qPart = parts.find(p => p.trim().startsWith('q='));
|
|
251
270
|
const q = qPart ? parseFloat(qPart.trim().slice(2)) : 1;
|
|
252
271
|
return { tag, q: isNaN(q) ? 0 : q };
|
|
@@ -270,6 +289,8 @@ export class UwsRequest {
|
|
|
270
289
|
export class UWebSocketsHttpServer {
|
|
271
290
|
app; // uWebSockets app instance
|
|
272
291
|
uws; // uWebSockets module reference (stored to avoid re-importing)
|
|
292
|
+
_limits;
|
|
293
|
+
multipartLimits;
|
|
273
294
|
listenSocket; // uWebSockets listen socket
|
|
274
295
|
globalMiddleware = [];
|
|
275
296
|
logger = createFrameworkLogger('UWSHttpServer');
|
|
@@ -327,6 +348,10 @@ export class UWebSocketsHttpServer {
|
|
|
327
348
|
this.maxBodySize = options.maxBodySize;
|
|
328
349
|
if (options.maxUploadSize)
|
|
329
350
|
this.maxUploadSize = options.maxUploadSize;
|
|
351
|
+
if (options.limits)
|
|
352
|
+
this._limits = options.limits;
|
|
353
|
+
if (options.limits?.multipart)
|
|
354
|
+
this.multipartLimits = options.limits.multipart;
|
|
330
355
|
this.initPromise = this.initialize(options);
|
|
331
356
|
}
|
|
332
357
|
// Direct router dispatch - called after global middleware without the
|
|
@@ -340,18 +365,34 @@ export class UWebSocketsHttpServer {
|
|
|
340
365
|
// routes are included.
|
|
341
366
|
setNativeRouteProvider(provider, onError) {
|
|
342
367
|
this.nativeRouteProvider = provider;
|
|
343
|
-
|
|
368
|
+
if (onError)
|
|
369
|
+
this.nativeErrorHandler = onError;
|
|
344
370
|
}
|
|
345
371
|
async initialize(options) {
|
|
346
372
|
try {
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
|
|
373
|
+
// The framework preloads the engine synchronously (loadNativeEngine) and
|
|
374
|
+
// injects it; loading here directly covers standalone construction.
|
|
375
|
+
// This server is uWS-shaped only: never probe @morojs/engine, whose
|
|
376
|
+
// Moro-shaped module would pass a generic load but has no App().
|
|
377
|
+
const uwsModule = options.engineModule ?? loadNativeEngine({ candidates: ['uWebSockets.js'] })?.module;
|
|
378
|
+
if (!uwsModule) {
|
|
379
|
+
throw new Error('uWebSockets.js is not available: ' +
|
|
380
|
+
(getNativeEngineLoadErrors().join('; ') || 'not installed'));
|
|
381
|
+
}
|
|
350
382
|
this.uws = uwsModule.default || uwsModule;
|
|
383
|
+
// A TLS config that was given only as inline PEM cannot be honored by
|
|
384
|
+
// uWS (it requires on-disk file paths). Surface it loudly rather than
|
|
385
|
+
// silently booting plain HTTP (the historical footgun).
|
|
386
|
+
if (options.sslInlineOnly) {
|
|
387
|
+
this.logger.error('uWebSockets.js requires TLS key/cert as FILE PATHS (keyFile/certFile ' +
|
|
388
|
+
'or key_file_name/cert_file_name); inline PEM is not supported. ' +
|
|
389
|
+
'Booting WITHOUT TLS - provide file paths or use engine: "moro"/"node".', 'Init');
|
|
390
|
+
}
|
|
351
391
|
if (options.ssl && options.ssl.key_file_name && options.ssl.cert_file_name) {
|
|
352
392
|
this.app = this.uws.SSLApp({
|
|
353
393
|
key_file_name: options.ssl.key_file_name,
|
|
354
394
|
cert_file_name: options.ssl.cert_file_name,
|
|
395
|
+
...(options.ssl.ca_file_name && { ca_file_name: options.ssl.ca_file_name }),
|
|
355
396
|
passphrase: options.ssl.passphrase,
|
|
356
397
|
});
|
|
357
398
|
this.isSsl = true;
|
|
@@ -361,15 +402,32 @@ export class UWebSocketsHttpServer {
|
|
|
361
402
|
this.app = this.uws.App();
|
|
362
403
|
this.logger.info('uWebSockets HTTP server created', 'Init');
|
|
363
404
|
}
|
|
405
|
+
// uWS has no knobs for TCP timeouts/backlog/maxConnections/maxHeaderSize;
|
|
406
|
+
// if the user set any, say so once at startup rather than silently
|
|
407
|
+
// ignoring them (the "no arbitrary caps" contract cuts both ways).
|
|
408
|
+
const ignored = [];
|
|
409
|
+
const t = options.limits?.timeouts ?? {};
|
|
410
|
+
if (t.request || t.idle || t.keepAlive || t.headers)
|
|
411
|
+
ignored.push('timeouts');
|
|
412
|
+
if (options.limits?.maxConnections)
|
|
413
|
+
ignored.push('maxConnections');
|
|
414
|
+
if (options.limits?.backlog)
|
|
415
|
+
ignored.push('backlog');
|
|
416
|
+
if (options.limits?.maxHeaderSize || options.limits?.maxHeaders)
|
|
417
|
+
ignored.push('header limits');
|
|
418
|
+
if (ignored.length) {
|
|
419
|
+
this.logger.info(`uWebSockets.js does not support these server options; they are ignored: ${ignored.join(', ')}`, 'Init');
|
|
420
|
+
}
|
|
364
421
|
// Setup generic route handler for all HTTP methods and paths
|
|
365
422
|
this.setupRouteHandlers();
|
|
366
423
|
}
|
|
367
424
|
catch (error) {
|
|
368
425
|
// Log helpful error message with installation instructions
|
|
369
|
-
this.logger.error('Failed to load
|
|
370
|
-
'
|
|
371
|
-
' npm install
|
|
372
|
-
'
|
|
426
|
+
this.logger.error('Failed to load the native HTTP engine\n' +
|
|
427
|
+
'Install it with:\n' +
|
|
428
|
+
' npm install @morojs/engine\n' +
|
|
429
|
+
'(or the legacy engine: npm install --save-dev github:uNetworking/uWebSockets.js#v20.52.0)\n' +
|
|
430
|
+
"Or set engine: 'node' in your server config to use the standard HTTP server.\n" +
|
|
373
431
|
'Error: ' +
|
|
374
432
|
(error instanceof Error ? error.message : String(error)), 'Init');
|
|
375
433
|
throw error; // Re-throw so framework.ts can catch and fallback
|
|
@@ -381,7 +439,7 @@ export class UWebSocketsHttpServer {
|
|
|
381
439
|
// method-specific routes registered later take priority in uWS's router
|
|
382
440
|
// (static > parameter > wildcard).
|
|
383
441
|
this.app.any('/*', (res, req) => {
|
|
384
|
-
this.handleRequest(req, res);
|
|
442
|
+
void this.handleRequest(req, res);
|
|
385
443
|
});
|
|
386
444
|
}
|
|
387
445
|
// Register fast-path routes (no middleware/auth/validation) directly on
|
|
@@ -412,7 +470,7 @@ export class UWebSocketsHttpServer {
|
|
|
412
470
|
if (this.globalMiddleware.length !== 0 ||
|
|
413
471
|
(this.hookManager &&
|
|
414
472
|
(this.hookManager.hasHooks === undefined || this.hookManager.hasHooks('request')))) {
|
|
415
|
-
this.handleRequest(req, res);
|
|
473
|
+
void this.handleRequest(req, res);
|
|
416
474
|
return;
|
|
417
475
|
}
|
|
418
476
|
this.handleNativeRoute(req, res, handler, methodInterned, paramNames, needsBody);
|
|
@@ -429,20 +487,28 @@ export class UWebSocketsHttpServer {
|
|
|
429
487
|
if (paramNames) {
|
|
430
488
|
const params = {};
|
|
431
489
|
for (let i = 0; i < paramNames.length; i++) {
|
|
432
|
-
|
|
490
|
+
const paramName = paramNames[i];
|
|
491
|
+
if (paramName === undefined)
|
|
492
|
+
continue;
|
|
493
|
+
params[paramName] = req.getParameter(i);
|
|
433
494
|
}
|
|
434
495
|
httpReq.params = params;
|
|
435
496
|
}
|
|
436
|
-
const httpRes = new UWebSocketsHttpServer.ResponsePrototype().init(res, req, this.logger);
|
|
497
|
+
const httpRes = new UWebSocketsHttpServer.ResponsePrototype().init(res, req, this.logger, this._compression);
|
|
498
|
+
httpRes._moroReq = httpReq;
|
|
437
499
|
if (needsBody) {
|
|
438
500
|
// Body routes always go async: snapshot headers, arm the abort hook,
|
|
439
501
|
// read the body, then run the handler
|
|
440
502
|
httpReq.materialize();
|
|
503
|
+
res._abortArmed = true;
|
|
441
504
|
res.onAborted(() => {
|
|
442
505
|
res.aborted = true;
|
|
443
506
|
const hook = res._abortHook;
|
|
444
507
|
if (hook)
|
|
445
508
|
hook();
|
|
509
|
+
const moroRes = res._moroRes;
|
|
510
|
+
if (moroRes)
|
|
511
|
+
moroRes._handleAbort();
|
|
446
512
|
});
|
|
447
513
|
const contentLength = req.getHeader('content-length');
|
|
448
514
|
const bodyPromise = contentLength && parseInt(contentLength) > 0
|
|
@@ -461,6 +527,7 @@ export class UWebSocketsHttpServer {
|
|
|
461
527
|
}
|
|
462
528
|
if (result !== undefined && !httpRes.headersSent)
|
|
463
529
|
httpRes.json(result);
|
|
530
|
+
return;
|
|
464
531
|
})
|
|
465
532
|
.catch(err => this.handleNativeRouteError(err, httpReq, httpRes, res));
|
|
466
533
|
return;
|
|
@@ -471,11 +538,15 @@ export class UWebSocketsHttpServer {
|
|
|
471
538
|
// Async handler: it is suspended at its first await - snapshot
|
|
472
539
|
// uWS-backed data and arm abort tracking before returning to the loop
|
|
473
540
|
httpReq.materialize();
|
|
541
|
+
res._abortArmed = true;
|
|
474
542
|
res.onAborted(() => {
|
|
475
543
|
res.aborted = true;
|
|
476
544
|
const hook = res._abortHook;
|
|
477
545
|
if (hook)
|
|
478
546
|
hook();
|
|
547
|
+
const moroRes = res._moroRes;
|
|
548
|
+
if (moroRes)
|
|
549
|
+
moroRes._handleAbort();
|
|
479
550
|
});
|
|
480
551
|
result.then(r => {
|
|
481
552
|
if (r !== undefined && !httpRes.headersSent && !res.aborted)
|
|
@@ -486,6 +557,23 @@ export class UWebSocketsHttpServer {
|
|
|
486
557
|
// Fully synchronous: single corked write, no header copy, no promise
|
|
487
558
|
httpRes.json(result);
|
|
488
559
|
}
|
|
560
|
+
// A synchronous handler that returned with the response still open
|
|
561
|
+
// (e.g. SSE: writeHead + periodic write) is about to hand an unfinished
|
|
562
|
+
// uWS response back to the event loop - uWS forbids that without an
|
|
563
|
+
// abort handler, and without one client disconnects are never noticed.
|
|
564
|
+
if (!httpRes.finished && !res.aborted && !res._abortArmed) {
|
|
565
|
+
res._abortArmed = true;
|
|
566
|
+
httpReq.materialize();
|
|
567
|
+
res.onAborted(() => {
|
|
568
|
+
res.aborted = true;
|
|
569
|
+
const hook = res._abortHook;
|
|
570
|
+
if (hook)
|
|
571
|
+
hook();
|
|
572
|
+
const moroRes = res._moroRes;
|
|
573
|
+
if (moroRes)
|
|
574
|
+
moroRes._handleAbort();
|
|
575
|
+
});
|
|
576
|
+
}
|
|
489
577
|
}
|
|
490
578
|
catch (err) {
|
|
491
579
|
this.handleNativeRouteError(err, httpReq, httpRes, res);
|
|
@@ -498,6 +586,12 @@ export class UWebSocketsHttpServer {
|
|
|
498
586
|
httpRes.end('{"success":false,"error":"Request entity too large"}');
|
|
499
587
|
return;
|
|
500
588
|
}
|
|
589
|
+
if (err?.statusCode === 400 && !res.aborted && !httpRes.headersSent) {
|
|
590
|
+
httpRes.statusCode = 400;
|
|
591
|
+
httpRes.setHeader('Content-Type', 'application/json');
|
|
592
|
+
httpRes.end('{"success":false,"error":"Invalid request body"}');
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
501
595
|
const sendFallback = () => {
|
|
502
596
|
if (!res.aborted && !httpRes.headersSent) {
|
|
503
597
|
// Same shape as the unified router's fast-path error response
|
|
@@ -527,6 +621,10 @@ export class UWebSocketsHttpServer {
|
|
|
527
621
|
const hook = res._abortHook;
|
|
528
622
|
if (hook)
|
|
529
623
|
hook();
|
|
624
|
+
// Fire 'close'/'aborted' on the moro wrappers (SSE/monitor cleanup)
|
|
625
|
+
const moroRes = res._moroRes;
|
|
626
|
+
if (moroRes)
|
|
627
|
+
moroRes._handleAbort();
|
|
530
628
|
});
|
|
531
629
|
let httpReq;
|
|
532
630
|
let httpRes;
|
|
@@ -534,6 +632,7 @@ export class UWebSocketsHttpServer {
|
|
|
534
632
|
// Create Moro-compatible request/response objects
|
|
535
633
|
httpReq = this.createMoroRequest(req, res);
|
|
536
634
|
httpRes = this.createMoroResponse(req, res);
|
|
635
|
+
httpRes._moroReq = httpReq;
|
|
537
636
|
const method = httpReq.method;
|
|
538
637
|
// Parse body only if there's actually a body (check content-length).
|
|
539
638
|
// Check first char for early exit (all body methods start with 'P')
|
|
@@ -544,6 +643,12 @@ export class UWebSocketsHttpServer {
|
|
|
544
643
|
// readBody awaits - snapshot uWS-backed data first
|
|
545
644
|
httpReq.materialize();
|
|
546
645
|
await this.readBody(res, httpReq);
|
|
646
|
+
// A client that disconnects mid-upload resolves readBody with a
|
|
647
|
+
// null/partial body; do not run hooks, middleware or the handler for
|
|
648
|
+
// a dead request (matches the native fast-path guard at ~line 625 and
|
|
649
|
+
// the Node backend, whose aborted stream rejects parseBody).
|
|
650
|
+
if (res.aborted)
|
|
651
|
+
return;
|
|
547
652
|
}
|
|
548
653
|
}
|
|
549
654
|
// Execute hooks before request processing - skipped entirely when none registered
|
|
@@ -594,6 +699,13 @@ export class UWebSocketsHttpServer {
|
|
|
594
699
|
httpRes.end('{"success":false,"error":"Request entity too large"}');
|
|
595
700
|
return;
|
|
596
701
|
}
|
|
702
|
+
// Malformed body: a client error (400), not a server error (500)
|
|
703
|
+
if (error?.statusCode === 400 && !res.aborted && httpRes && !httpRes.headersSent) {
|
|
704
|
+
httpRes.statusCode = 400;
|
|
705
|
+
httpRes.setHeader('Content-Type', 'application/json');
|
|
706
|
+
httpRes.end('{"success":false,"error":"Invalid request body"}');
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
597
709
|
this.logger.error(`Request handling error: ${error instanceof Error ? error.message : String(error)}`, 'RequestError');
|
|
598
710
|
// Send error response if not already sent
|
|
599
711
|
if (!res.aborted) {
|
|
@@ -620,12 +732,31 @@ export class UWebSocketsHttpServer {
|
|
|
620
732
|
// materialize() snapshots them before the first await
|
|
621
733
|
return new UwsRequest(this, req, method, url, queryString);
|
|
622
734
|
}
|
|
735
|
+
// Strip CR/LF from a header value to prevent response-splitting / header
|
|
736
|
+
// injection, matching Node's setHeader ERR_INVALID_CHAR guarantee. Guarded
|
|
737
|
+
// with indexOf so the common (clean) path allocates nothing on the hot path.
|
|
738
|
+
static sanitizeHeaderValue(value) {
|
|
739
|
+
return value.indexOf('\r') === -1 && value.indexOf('\n') === -1
|
|
740
|
+
? value
|
|
741
|
+
: value.replace(/[\r\n]/g, '');
|
|
742
|
+
}
|
|
623
743
|
// Optimized helper to write headers
|
|
624
744
|
static writeHeaders(res, headers) {
|
|
625
745
|
// Performance: for...in is the fastest way to iterate response headers
|
|
626
746
|
for (const key in headers) {
|
|
627
747
|
const value = headers[key];
|
|
628
|
-
|
|
748
|
+
if (Array.isArray(value)) {
|
|
749
|
+
// Emit one header line per element instead of comma-joining. RFC 6265
|
|
750
|
+
// forbids folding Set-Cookie, and browsers parse a folded
|
|
751
|
+
// `a=1; Path=/, b=2` as a single cookie, silently dropping the second
|
|
752
|
+
// (session + CSRF is the canonical breakage). uWS writes a separate
|
|
753
|
+
// line per writeHeader call with the same key.
|
|
754
|
+
for (let i = 0; i < value.length; i++) {
|
|
755
|
+
res.writeHeader(key, UWebSocketsHttpServer.sanitizeHeaderValue(String(value[i])));
|
|
756
|
+
}
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
res.writeHeader(key, UWebSocketsHttpServer.sanitizeHeaderValue(String(value)));
|
|
629
760
|
}
|
|
630
761
|
}
|
|
631
762
|
// Helper to get status string (cached for performance)
|
|
@@ -633,14 +764,25 @@ export class UWebSocketsHttpServer {
|
|
|
633
764
|
return UWebSocketsHttpServer.STATUS_STRINGS.get(code) || `${code} OK`;
|
|
634
765
|
}
|
|
635
766
|
// Pre-define methods on prototype instead of creating new closures for each request
|
|
636
|
-
static ResponsePrototype = class {
|
|
767
|
+
static ResponsePrototype = class extends LazyEventEmitter {
|
|
637
768
|
headersSent = false;
|
|
638
769
|
statusCode = 200;
|
|
639
770
|
responseHeaders = {};
|
|
640
771
|
_res;
|
|
641
772
|
_req;
|
|
642
773
|
_logger;
|
|
643
|
-
|
|
774
|
+
// Terminal-write latch: headersSent means "head flushed" (true mid-stream
|
|
775
|
+
// after writeHead/write), _ended means the body is complete
|
|
776
|
+
_ended = false;
|
|
777
|
+
_drainArmed = false;
|
|
778
|
+
// Accept-Encoding captured eagerly (uWS req is valid only synchronously)
|
|
779
|
+
// and the server's compression settings, for buffered-response compression.
|
|
780
|
+
_acceptEncoding = undefined;
|
|
781
|
+
_compression = undefined;
|
|
782
|
+
// The UwsRequest wrapper, linked by the server so lifecycle events reach
|
|
783
|
+
// req.on('close') listeners (SSE cleanup, monitors)
|
|
784
|
+
_moroReq = null;
|
|
785
|
+
init(res, req, logger, compression) {
|
|
644
786
|
this.headersSent = false;
|
|
645
787
|
this.statusCode = 200;
|
|
646
788
|
// Fresh object (a delete-loop reset would push the object into V8
|
|
@@ -649,8 +791,122 @@ export class UWebSocketsHttpServer {
|
|
|
649
791
|
this._res = res;
|
|
650
792
|
this._req = req;
|
|
651
793
|
this._logger = logger;
|
|
794
|
+
this._ended = false;
|
|
795
|
+
this._drainArmed = false;
|
|
796
|
+
this._compression = compression;
|
|
797
|
+
// Capture Accept-Encoding now: the native req is valid only during the
|
|
798
|
+
// synchronous handler entry, but compression resolves asynchronously.
|
|
799
|
+
this._acceptEncoding =
|
|
800
|
+
compression?.enabled && req ? req.getHeader('accept-encoding') || undefined : undefined;
|
|
801
|
+
// Route client aborts to the wrapper so 'close' fires (see onAborted)
|
|
802
|
+
res._moroRes = this;
|
|
652
803
|
return this;
|
|
653
804
|
}
|
|
805
|
+
// Buffered-response compression for uWS (parity with the engine/Node
|
|
806
|
+
// paths). Returns true when it took over the write (async), false when the
|
|
807
|
+
// caller should proceed with its normal synchronous cork+end.
|
|
808
|
+
_tryCompressedEnd(body, contentType) {
|
|
809
|
+
const s = this._compression;
|
|
810
|
+
if (!s || !s.enabled)
|
|
811
|
+
return false;
|
|
812
|
+
const bytes = typeof body === 'string' ? Buffer.byteLength(body) : body.length;
|
|
813
|
+
if (bytes < s.threshold)
|
|
814
|
+
return false;
|
|
815
|
+
if ('content-encoding' in this.responseHeaders)
|
|
816
|
+
return false;
|
|
817
|
+
if (this.statusCode === 204 || this.statusCode === 304)
|
|
818
|
+
return false;
|
|
819
|
+
const ctHeader = this.responseHeaders['content-type'];
|
|
820
|
+
const ct = contentType ?? (Array.isArray(ctHeader) ? ctHeader[0] : ctHeader);
|
|
821
|
+
if (!isCompressible(ct))
|
|
822
|
+
return false;
|
|
823
|
+
const encoding = negotiateEncoding(this._acceptEncoding, s.encodings);
|
|
824
|
+
if (!encoding)
|
|
825
|
+
return false;
|
|
826
|
+
void compressBuffer(body, encoding, s.level)
|
|
827
|
+
.then(compressed => {
|
|
828
|
+
if (this._ended || this._res.aborted)
|
|
829
|
+
return;
|
|
830
|
+
this.responseHeaders['content-encoding'] = encoding;
|
|
831
|
+
const vary = this.responseHeaders['vary'];
|
|
832
|
+
this.responseHeaders['vary'] = vary ? `${vary}, Accept-Encoding` : 'Accept-Encoding';
|
|
833
|
+
this._res.cork(() => {
|
|
834
|
+
this._res.writeStatus(UWebSocketsHttpServer.getStatusString(this.statusCode));
|
|
835
|
+
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
836
|
+
this._res.end(compressed);
|
|
837
|
+
});
|
|
838
|
+
this.headersSent = true;
|
|
839
|
+
this._ended = true;
|
|
840
|
+
this._emitDone();
|
|
841
|
+
})
|
|
842
|
+
.catch((err) => {
|
|
843
|
+
this._logger?.error?.(`uWS response compression failed: ${err instanceof Error ? err.message : String(err)}`, 'Response');
|
|
844
|
+
if (this._ended || this._res.aborted)
|
|
845
|
+
return;
|
|
846
|
+
this._res.cork(() => {
|
|
847
|
+
this._res.writeStatus(UWebSocketsHttpServer.getStatusString(this.statusCode));
|
|
848
|
+
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
849
|
+
this._res.end(body);
|
|
850
|
+
});
|
|
851
|
+
this.headersSent = true;
|
|
852
|
+
this._ended = true;
|
|
853
|
+
this._emitDone();
|
|
854
|
+
});
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
get writableEnded() {
|
|
858
|
+
return this._ended;
|
|
859
|
+
}
|
|
860
|
+
get writableFinished() {
|
|
861
|
+
return this._ended;
|
|
862
|
+
}
|
|
863
|
+
get finished() {
|
|
864
|
+
return this._ended;
|
|
865
|
+
}
|
|
866
|
+
// Node ServerResponse lifecycle: 'finish' then 'close' after the terminal
|
|
867
|
+
// write; the request wrapper's 'close' fires too (IncomingMessage parity).
|
|
868
|
+
// No listeners -> two null checks, nothing else.
|
|
869
|
+
_emitDone() {
|
|
870
|
+
this._ended = true;
|
|
871
|
+
if (this._events) {
|
|
872
|
+
this.emit('finish');
|
|
873
|
+
this.emit('close');
|
|
874
|
+
}
|
|
875
|
+
const req = this._moroReq;
|
|
876
|
+
if (req && req._events)
|
|
877
|
+
req.emit('close');
|
|
878
|
+
}
|
|
879
|
+
// Client aborted: 'close' without 'finish' (Node semantics), plus
|
|
880
|
+
// 'aborted'/'close' on the request wrapper
|
|
881
|
+
_handleAbort() {
|
|
882
|
+
if (this._ended)
|
|
883
|
+
return;
|
|
884
|
+
this._ended = true;
|
|
885
|
+
if (this._events)
|
|
886
|
+
this.emit('close');
|
|
887
|
+
const req = this._moroReq;
|
|
888
|
+
if (req && req._events) {
|
|
889
|
+
req.emit('aborted');
|
|
890
|
+
req.emit('close');
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
// Backpressure: surface uWS's onWritable as the 'drain' event pipe()
|
|
894
|
+
// and manual streaming loops wait on
|
|
895
|
+
_armDrain() {
|
|
896
|
+
if (this._drainArmed)
|
|
897
|
+
return;
|
|
898
|
+
this._drainArmed = true;
|
|
899
|
+
try {
|
|
900
|
+
this._res.onWritable(() => {
|
|
901
|
+
this._drainArmed = false;
|
|
902
|
+
this.emit('drain');
|
|
903
|
+
return true;
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
catch {
|
|
907
|
+
this._drainArmed = false;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
654
910
|
status(code) {
|
|
655
911
|
this.statusCode = code;
|
|
656
912
|
return this;
|
|
@@ -678,7 +934,12 @@ export class UWebSocketsHttpServer {
|
|
|
678
934
|
return;
|
|
679
935
|
// Fast-path JSON serialization for common API patterns
|
|
680
936
|
let body;
|
|
681
|
-
|
|
937
|
+
// The interpolated fast path is only valid when `success` is a real
|
|
938
|
+
// boolean (a string/number would produce invalid JSON like
|
|
939
|
+
// {"success":yes,...}), and each interpolated field must be defined
|
|
940
|
+
// (JSON.stringify(undefined) yields the literal token "undefined", which
|
|
941
|
+
// would corrupt the body). Otherwise fall through to JSON.stringify.
|
|
942
|
+
if (data && typeof data === 'object' && typeof data.success === 'boolean') {
|
|
682
943
|
let keyCount = 0;
|
|
683
944
|
let hasData = false;
|
|
684
945
|
let hasError = false;
|
|
@@ -694,16 +955,24 @@ export class UWebSocketsHttpServer {
|
|
|
694
955
|
hasTotal = true;
|
|
695
956
|
}
|
|
696
957
|
}
|
|
697
|
-
if (keyCount === 3 &&
|
|
958
|
+
if (keyCount === 3 &&
|
|
959
|
+
hasData &&
|
|
960
|
+
hasError &&
|
|
961
|
+
data.data !== undefined &&
|
|
962
|
+
data.error !== undefined) {
|
|
698
963
|
body = `{"success":${data.success},"data":${JSON.stringify(data.data)},"error":${JSON.stringify(data.error)}}`;
|
|
699
964
|
}
|
|
700
|
-
else if (keyCount === 3 &&
|
|
965
|
+
else if (keyCount === 3 &&
|
|
966
|
+
hasData &&
|
|
967
|
+
hasTotal &&
|
|
968
|
+
data.data !== undefined &&
|
|
969
|
+
typeof data.total === 'number') {
|
|
701
970
|
body = `{"success":${data.success},"data":${JSON.stringify(data.data)},"total":${data.total}}`;
|
|
702
971
|
}
|
|
703
|
-
else if (keyCount === 2 && hasData) {
|
|
972
|
+
else if (keyCount === 2 && hasData && data.data !== undefined) {
|
|
704
973
|
body = `{"success":${data.success},"data":${JSON.stringify(data.data)}}`;
|
|
705
974
|
}
|
|
706
|
-
else if (keyCount === 2 && hasError) {
|
|
975
|
+
else if (keyCount === 2 && hasError && data.error !== undefined) {
|
|
707
976
|
body = `{"success":${data.success},"error":${JSON.stringify(data.error)}}`;
|
|
708
977
|
}
|
|
709
978
|
else {
|
|
@@ -717,12 +986,17 @@ export class UWebSocketsHttpServer {
|
|
|
717
986
|
this.responseHeaders['content-type'] = 'application/json';
|
|
718
987
|
}
|
|
719
988
|
try {
|
|
989
|
+
// Compression (when enabled + compressible + accepted) takes over the
|
|
990
|
+
// write asynchronously; otherwise the synchronous corked path runs.
|
|
991
|
+
if (this._tryCompressedEnd(body, 'application/json'))
|
|
992
|
+
return;
|
|
720
993
|
this._res.cork(() => {
|
|
721
994
|
this._res.writeStatus(UWebSocketsHttpServer.getStatusString(this.statusCode));
|
|
722
995
|
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
723
996
|
this._res.end(body);
|
|
724
997
|
});
|
|
725
998
|
this.headersSent = true;
|
|
999
|
+
this._emitDone();
|
|
726
1000
|
}
|
|
727
1001
|
catch {
|
|
728
1002
|
this._logger.error('Failed to send JSON response', 'ResponseError');
|
|
@@ -733,30 +1007,103 @@ export class UWebSocketsHttpServer {
|
|
|
733
1007
|
return;
|
|
734
1008
|
const body = typeof data === 'string' ? data : data.toString();
|
|
735
1009
|
try {
|
|
1010
|
+
if (this._tryCompressedEnd(body, undefined))
|
|
1011
|
+
return;
|
|
736
1012
|
this._res.cork(() => {
|
|
737
1013
|
this._res.writeStatus(UWebSocketsHttpServer.getStatusString(this.statusCode));
|
|
738
1014
|
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
739
1015
|
this._res.end(body);
|
|
740
1016
|
});
|
|
741
1017
|
this.headersSent = true;
|
|
1018
|
+
this._emitDone();
|
|
742
1019
|
}
|
|
743
1020
|
catch {
|
|
744
1021
|
this._logger.error('Failed to send response', 'ResponseError');
|
|
745
1022
|
}
|
|
746
1023
|
}
|
|
1024
|
+
// Flush status + headers without ending the response (Node streaming
|
|
1025
|
+
// entry point - SSE and manual chunked responses start here). Supports
|
|
1026
|
+
// both (code, headers) and (code, statusMessage, headers) signatures;
|
|
1027
|
+
// uWS derives the reason phrase from the status string, so a custom
|
|
1028
|
+
// message is folded into it.
|
|
1029
|
+
writeHead(statusCode, reasonOrHeaders, maybeHeaders) {
|
|
1030
|
+
if (this.headersSent || this._res.aborted)
|
|
1031
|
+
return this;
|
|
1032
|
+
this.statusCode = statusCode;
|
|
1033
|
+
const headers = reasonOrHeaders && typeof reasonOrHeaders === 'object' ? reasonOrHeaders : maybeHeaders;
|
|
1034
|
+
if (headers) {
|
|
1035
|
+
for (const key of Object.keys(headers)) {
|
|
1036
|
+
this.setHeader(key, headers[key]);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
try {
|
|
1040
|
+
this._res.cork(() => {
|
|
1041
|
+
const status = typeof reasonOrHeaders === 'string'
|
|
1042
|
+
? `${statusCode} ${reasonOrHeaders}`
|
|
1043
|
+
: UWebSocketsHttpServer.getStatusString(statusCode);
|
|
1044
|
+
this._res.writeStatus(status);
|
|
1045
|
+
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
1046
|
+
});
|
|
1047
|
+
this.headersSent = true;
|
|
1048
|
+
}
|
|
1049
|
+
catch {
|
|
1050
|
+
this._logger.error('Failed to write response head', 'ResponseError');
|
|
1051
|
+
}
|
|
1052
|
+
return this;
|
|
1053
|
+
}
|
|
1054
|
+
// Stream a body chunk, flushing the head first if needed. Returns uWS's
|
|
1055
|
+
// backpressure signal like Node's Writable.write; 'drain' fires when the
|
|
1056
|
+
// socket can accept more.
|
|
1057
|
+
write(chunk, encoding, callback) {
|
|
1058
|
+
if (typeof encoding === 'function') {
|
|
1059
|
+
callback = encoding;
|
|
1060
|
+
encoding = undefined;
|
|
1061
|
+
}
|
|
1062
|
+
if (this._ended || this._res.aborted)
|
|
1063
|
+
return false;
|
|
1064
|
+
let ok = true;
|
|
1065
|
+
try {
|
|
1066
|
+
this._res.cork(() => {
|
|
1067
|
+
if (!this.headersSent) {
|
|
1068
|
+
this._res.writeStatus(UWebSocketsHttpServer.getStatusString(this.statusCode));
|
|
1069
|
+
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
1070
|
+
this.headersSent = true;
|
|
1071
|
+
}
|
|
1072
|
+
ok = this._res.write(chunk);
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
catch {
|
|
1076
|
+
this._logger.error('Failed to write response chunk', 'ResponseError');
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
if (!ok)
|
|
1080
|
+
this._armDrain();
|
|
1081
|
+
if (typeof callback === 'function')
|
|
1082
|
+
callback();
|
|
1083
|
+
return ok;
|
|
1084
|
+
}
|
|
747
1085
|
end(data, encoding, callback) {
|
|
748
|
-
if (
|
|
1086
|
+
if (typeof encoding === 'function') {
|
|
1087
|
+
callback = encoding;
|
|
1088
|
+
encoding = undefined;
|
|
1089
|
+
}
|
|
1090
|
+
// Guard on _ended, not headersSent: in streaming mode (writeHead/write)
|
|
1091
|
+
// the head is already flushed and end() must still complete the body
|
|
1092
|
+
if (this._ended || this._res.aborted) {
|
|
749
1093
|
if (typeof callback === 'function')
|
|
750
1094
|
callback();
|
|
751
1095
|
return this;
|
|
752
1096
|
}
|
|
753
1097
|
try {
|
|
754
1098
|
this._res.cork(() => {
|
|
755
|
-
|
|
756
|
-
|
|
1099
|
+
if (!this.headersSent) {
|
|
1100
|
+
this._res.writeStatus(UWebSocketsHttpServer.getStatusString(this.statusCode));
|
|
1101
|
+
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
1102
|
+
this.headersSent = true;
|
|
1103
|
+
}
|
|
757
1104
|
this._res.end(data || '');
|
|
758
1105
|
});
|
|
759
|
-
this.
|
|
1106
|
+
this._emitDone();
|
|
760
1107
|
if (typeof callback === 'function')
|
|
761
1108
|
callback();
|
|
762
1109
|
}
|
|
@@ -775,11 +1122,12 @@ export class UWebSocketsHttpServer {
|
|
|
775
1122
|
try {
|
|
776
1123
|
this._res.cork(() => {
|
|
777
1124
|
this._res.writeStatus(UWebSocketsHttpServer.getStatusString(redirectCode));
|
|
778
|
-
this._res.writeHeader('Location', url);
|
|
1125
|
+
this._res.writeHeader('Location', UWebSocketsHttpServer.sanitizeHeaderValue(url));
|
|
779
1126
|
UWebSocketsHttpServer.writeHeaders(this._res, this.responseHeaders);
|
|
780
1127
|
this._res.end();
|
|
781
1128
|
});
|
|
782
1129
|
this.headersSent = true;
|
|
1130
|
+
this._emitDone();
|
|
783
1131
|
}
|
|
784
1132
|
catch {
|
|
785
1133
|
this._logger.error('Failed to send redirect', 'ResponseError');
|
|
@@ -918,32 +1266,25 @@ export class UWebSocketsHttpServer {
|
|
|
918
1266
|
},
|
|
919
1267
|
});
|
|
920
1268
|
}
|
|
921
|
-
// EventEmitter stubs for middleware compatibility
|
|
922
|
-
on(_event, _callback) {
|
|
923
|
-
return this;
|
|
924
|
-
}
|
|
925
|
-
once(_event, _callback) {
|
|
926
|
-
return this;
|
|
927
|
-
}
|
|
928
|
-
emit(_event, ..._args) {
|
|
929
|
-
return true;
|
|
930
|
-
}
|
|
931
|
-
removeListener(_event, _callback) {
|
|
932
|
-
return this;
|
|
933
|
-
}
|
|
934
1269
|
cookie(name, value, options) {
|
|
935
|
-
//
|
|
936
|
-
|
|
1270
|
+
// Percent-encode the value (matches the Node path and Express) so a value
|
|
1271
|
+
// containing ';'/',' can't inject cookie attributes and control chars
|
|
1272
|
+
// don't corrupt the Set-Cookie header.
|
|
1273
|
+
const parts = [name, '=', encodeURIComponent(value)];
|
|
937
1274
|
if (options) {
|
|
938
|
-
|
|
1275
|
+
// maxAge: 0 is meaningful (immediate expiry - clearCookie relies on it)
|
|
1276
|
+
if (options.maxAge !== undefined && options.maxAge !== null) {
|
|
939
1277
|
parts.push('; Max-Age=', String(options.maxAge));
|
|
940
1278
|
}
|
|
1279
|
+
if (options.expires) {
|
|
1280
|
+
const expires = options.expires instanceof Date
|
|
1281
|
+
? options.expires.toUTCString()
|
|
1282
|
+
: String(options.expires);
|
|
1283
|
+
parts.push('; Expires=', expires);
|
|
1284
|
+
}
|
|
941
1285
|
if (options.domain) {
|
|
942
1286
|
parts.push('; Domain=', options.domain);
|
|
943
1287
|
}
|
|
944
|
-
if (options.path) {
|
|
945
|
-
parts.push('; Path=', options.path);
|
|
946
|
-
}
|
|
947
1288
|
if (options.secure) {
|
|
948
1289
|
parts.push('; Secure');
|
|
949
1290
|
}
|
|
@@ -954,6 +1295,9 @@ export class UWebSocketsHttpServer {
|
|
|
954
1295
|
parts.push('; SameSite=', options.sameSite);
|
|
955
1296
|
}
|
|
956
1297
|
}
|
|
1298
|
+
// Path defaults to '/' (Express behavior) so clearCookie() from a nested
|
|
1299
|
+
// route can clear a cookie originally set at the site root.
|
|
1300
|
+
parts.push('; Path=', options?.path ?? '/');
|
|
957
1301
|
const cookie = parts.join('');
|
|
958
1302
|
const lowerKey = 'set-cookie';
|
|
959
1303
|
const existing = this.responseHeaders[lowerKey];
|
|
@@ -1109,8 +1453,8 @@ export class UWebSocketsHttpServer {
|
|
|
1109
1453
|
headersSent: this.headersSent,
|
|
1110
1454
|
statusCode: this.statusCode,
|
|
1111
1455
|
headers: this.responseHeaders,
|
|
1112
|
-
finished: this.
|
|
1113
|
-
writable: !this.
|
|
1456
|
+
finished: this._ended,
|
|
1457
|
+
writable: !this._ended,
|
|
1114
1458
|
};
|
|
1115
1459
|
}
|
|
1116
1460
|
};
|
|
@@ -1181,15 +1525,34 @@ export class UWebSocketsHttpServer {
|
|
|
1181
1525
|
});
|
|
1182
1526
|
httpReq.body = body;
|
|
1183
1527
|
}
|
|
1528
|
+
else if (isMultipart) {
|
|
1529
|
+
// Shared parser (same as the Node http server) - stringifying
|
|
1530
|
+
// multipart would corrupt binary uploads and lose fields/files.
|
|
1531
|
+
// parseMultipart retains slices of the payload as file Buffers;
|
|
1532
|
+
// the single-chunk fast path (buffer === view) aliases uWS-owned
|
|
1533
|
+
// memory that is recycled after this callback returns, so copy
|
|
1534
|
+
// it first. Concatenated multi-chunk buffers are already stable.
|
|
1535
|
+
const stable = buffer === view ? Buffer.from(buffer) : buffer;
|
|
1536
|
+
httpReq.body = parseMultipartBuffer(stable, contentType, this.multipartLimits);
|
|
1537
|
+
}
|
|
1184
1538
|
else {
|
|
1185
1539
|
httpReq.body = buffer.toString('utf-8');
|
|
1186
1540
|
}
|
|
1187
1541
|
finish(resolve);
|
|
1188
1542
|
}
|
|
1189
|
-
catch {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1543
|
+
catch (parseError) {
|
|
1544
|
+
// A limit error (multipart maxParts/maxFiles/maxFileSize) carries its
|
|
1545
|
+
// own 413 - preserve it rather than masking it as a generic 400.
|
|
1546
|
+
if (parseError && typeof parseError.statusCode === 'number') {
|
|
1547
|
+
finish(() => reject(parseError));
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
// A malformed body is a client error: reject with 400 (parity with
|
|
1551
|
+
// the engine and Node transports) rather than silently handing the
|
|
1552
|
+
// handler a null body it may treat as "no input".
|
|
1553
|
+
const error = new Error(`Invalid request body: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
1554
|
+
error.statusCode = 400;
|
|
1555
|
+
finish(() => reject(error));
|
|
1193
1556
|
}
|
|
1194
1557
|
});
|
|
1195
1558
|
// Chain onto the request-lifetime abort hook (registered in handleRequest
|
|
@@ -1250,10 +1613,14 @@ export class UWebSocketsHttpServer {
|
|
|
1250
1613
|
setHookManager(hookManager) {
|
|
1251
1614
|
this.hookManager = hookManager;
|
|
1252
1615
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1616
|
+
/** @internal buffered-response compression settings (read at init). */
|
|
1617
|
+
_compression = resolveCompressionSettings();
|
|
1618
|
+
configurePerformance(config) {
|
|
1619
|
+
// uWS batches the request hot path natively; response compression is the
|
|
1620
|
+
// one thing it doesn't do itself, so wire that here (parity with Node/
|
|
1621
|
+
// engine). Buffered responses only - streaming stays raw.
|
|
1622
|
+
this._compression = resolveCompressionSettings(config);
|
|
1623
|
+
this.logger.debug(`Performance configured (compression ${this._compression.enabled ? 'on' : 'off'})`, 'Config');
|
|
1257
1624
|
}
|
|
1258
1625
|
listen(port, hostOrCallback, callback) {
|
|
1259
1626
|
// Wrap in async to await init
|
|
@@ -1280,24 +1647,36 @@ export class UWebSocketsHttpServer {
|
|
|
1280
1647
|
// uWebSockets.js automatically enables SO_REUSEPORT when in cluster mode
|
|
1281
1648
|
// Do NOT pass listenOptions in cluster mode - let uWS handle it automatically
|
|
1282
1649
|
// In non-cluster mode, we don't need SO_REUSEPORT
|
|
1283
|
-
|
|
1650
|
+
const onListen = (token) => {
|
|
1284
1651
|
if (token) {
|
|
1285
1652
|
this.listenSocket = token;
|
|
1286
1653
|
this.isListening = true;
|
|
1287
1654
|
const clusterInfo = isClusterWorker ? ` (worker ${process.pid})` : '';
|
|
1288
|
-
this.logger.info(`uWebSockets HTTP server listening on
|
|
1655
|
+
this.logger.info(`uWebSockets HTTP server listening on ${host}:${port}${clusterInfo}`, 'Listen');
|
|
1289
1656
|
if (cb)
|
|
1290
1657
|
cb();
|
|
1291
1658
|
}
|
|
1292
1659
|
else {
|
|
1293
1660
|
const clusterInfo = isClusterWorker ? ` (worker ${process.pid})` : '';
|
|
1294
|
-
this.logger.error(`Failed to listen on
|
|
1661
|
+
this.logger.error(`Failed to listen on ${host}:${port}${clusterInfo}`, 'Listen');
|
|
1295
1662
|
// Don't throw in cluster workers - let them fail gracefully
|
|
1296
1663
|
if (!isClusterWorker) {
|
|
1297
|
-
throw new Error(`Failed to bind to
|
|
1664
|
+
throw new Error(`Failed to bind to ${host}:${port}`);
|
|
1298
1665
|
}
|
|
1299
1666
|
}
|
|
1300
|
-
}
|
|
1667
|
+
};
|
|
1668
|
+
// Honor the requested bind host. For the all-interfaces default (and
|
|
1669
|
+
// cluster mode, where uWS auto-enables SO_REUSEPORT) keep the 2-arg
|
|
1670
|
+
// form so that behavior is unchanged; when a specific host is given
|
|
1671
|
+
// (e.g. 127.0.0.1 for an internal/admin service behind a proxy) use
|
|
1672
|
+
// uWS's (host, port, cb) overload so we don't silently bind 0.0.0.0.
|
|
1673
|
+
const bindAllInterfaces = host === '0.0.0.0' || host === '::';
|
|
1674
|
+
if (bindAllInterfaces) {
|
|
1675
|
+
this.app.listen(port, onListen);
|
|
1676
|
+
}
|
|
1677
|
+
else {
|
|
1678
|
+
this.app.listen(host, port, onListen);
|
|
1679
|
+
}
|
|
1301
1680
|
})
|
|
1302
1681
|
.catch(error => {
|
|
1303
1682
|
this.logger.error('Failed to initialize server before listen', 'Listen', {
|