Skip to content

feat: implement advanced HTTP features (multipart, static files, CORS, compression)#23

Merged
VikramAditya33 merged 1 commit into
v2-http-supportfrom
v2-p6
Apr 23, 2026
Merged

feat: implement advanced HTTP features (multipart, static files, CORS, compression)#23
VikramAditya33 merged 1 commit into
v2-http-supportfrom
v2-p6

Conversation

@VikramAditya33

@VikramAditya33 VikramAditya33 commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

Implements multipart form data handling, static file serving, CORS support, and compression for the uWestJS HTTP platform.

Features Implemented

Multipart/Form-Data Support

  • Streaming file upload handling with busboy integration
  • Field and file parsing with async handlers
  • Backpressure control for large uploads
  • Memory-efficient streaming to disk or processing

Static File Serving

  • File streaming with range request support (partial content)
  • ETag generation and conditional request handling (304 Not Modified)
  • Cache-Control headers with configurable max-age
  • Security: directory traversal prevention and dotfile handling
  • Worker pool for optimized small file serving (<768KB)
  • MIME type detection and content-type headers

CORS Support

  • Origin validation (string, array, boolean, or function)
  • Preflight request handling (OPTIONS)
  • Configurable allowed methods, headers, and exposed headers
  • Credentials support
  • Max-age caching for preflight responses

Compression Support (Fixes #24)

  • Automatic content encoding (gzip, deflate, brotli)
  • Accept-Encoding header negotiation
  • Configurable compression levels and thresholds
  • Streaming compression for large responses

Hybrid Readable Stream (Fixes #21)

  • Three-mode operation: awaiting, buffering, streaming
  • Lazy stream activation (only when needed)
  • Automatic mode switching based on usage
  • Proper backpressure handling with pause/resume
  • Memory-efficient buffering with watermark control

File Worker Pool

  • Multi-threaded file reading for small files
  • Round-robin worker selection with idle-first strategy
  • Automatic fallback to streaming for large files
  • Configurable worker count and size threshold

Summary by CodeRabbit

Release Notes

  • New Features

    • Added HTTP request/response compression support (gzip, deflate, brotli).
    • Added CORS (Cross-Origin Resource Sharing) request handling.
    • Added static file serving with HTTP caching, conditional requests, and range support.
    • Added multipart form data parsing.
    • Added fastAbort option for terminating invalid requests immediately.
    • Extended CORS origin validation to support asynchronous validators.
  • Tests

    • Added comprehensive test suites for compression, CORS, static files, multipart parsing, and file operations.

@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58376821-19e7-41d3-aa5b-7cfd5488aead

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v2-p6

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (4)
src/platform/file-worker-pool.ts (1)

12-32: Worker code uses fs.readFileSync — fine for a worker, but no size cap.

Since FileWorkerPool is documented as serving small files (<768KB per the PR description), consider asserting a max size in the worker (e.g., fs.statSync gate or fs.openSync + bounded read) so a caller passing a huge path doesn't block the worker thread on a multi-hundred-MB read and stall the pool. Not a blocker, but useful defense-in-depth given the static-file handler fallback path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/file-worker-pool.ts` around lines 12 - 32, The inline workerCode
currently uses fs.readFileSync without any size cap which can block worker
threads on large files; update the message handler inside workerCode (the
parentPort.on('message', ... ) block) to stat the file first (e.g.,
fs.statSync(message.path)) and reject files larger than 768 * 1024 bytes by
posting back an error { key: message.key, err: 'file too large' }, otherwise
perform the read (use a bounded read or continue with readFileSync after the
size check) and send the ArrayBuffer as before; ensure the error path matches
the existing postMessage shape so FileWorkerPool's callers receive the error.
src/platform/file-worker-pool.spec.ts (1)

126-162: Consider adding a test for worker-crash recovery.

Current error tests cover invalid paths and termination, but not the case where a worker thread emits error or exits unexpectedly with pending tasks. Adding such a test (e.g., forcing a worker into an unrecoverable state and asserting that subsequent readFile() calls still succeed) would catch the "dead worker not replaced" issue noted on file-worker-pool.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/file-worker-pool.spec.ts` around lines 126 - 162, Add a test
that forces a worker to crash and verifies the pool recovers: instantiate
FileWorkerPool (use 1 or 2 workers), start a pending readFile() task, then
simulate a worker `error`/unexpected exit for that worker (or kill the worker
thread) to trigger the pool's crash handler, assert the pending task is rejected
or handled per pool semantics, then call readFile() again and assert it resolves
to the expected content to prove the dead worker was replaced; reference
FileWorkerPool, readFile, and terminate in the test and ensure you await pool
termination at the end.
src/platform/route-registry.ts (1)

842-852: LGTM — replacement-warning is a nice touch.

One small thought: since corsHandler is set once and doesn't support un-registering, consider also exposing a clearCorsHandler() (or accepting undefined) for test isolation and dynamic reconfiguration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/route-registry.ts` around lines 842 - 852, Add a way to clear
the registered CORS handler for test isolation and dynamic reconfiguration by
adding a public clearCorsHandler(): void method that sets the internal
corsHandler to undefined and logs a debug/info message; keep
registerCorsHandler(handler: CorsHandler) as-is but ensure the corsHandler
field’s type allows undefined (e.g., CorsHandler | undefined) so both
registerCorsHandler and clearCorsHandler compile and behave correctly — changes
should reference the existing registerCorsHandler method and the corsHandler
property.
src/platform/uws-platform.adapter.ts (1)

82-102: Unused corsHandler field and debug-only logging.

  • private corsHandler?: CorsHandler (Line 87) is assigned in enableCors() but never read elsewhere in the adapter — the handler is owned by routeRegistry after registration. Either remove it or expose via a getter for debugging/testing parity with getRouteRegistry().
  • warnOnce uses console.warn/console.log directly. Consider routing through a Logger (as route-registry does) for consistency and so consumers can silence/redirect them.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/uws-platform.adapter.ts` around lines 82 - 102, Remove or expose
the unused corsHandler: either delete the private corsHandler?: CorsHandler
field (since enableCors() registers the handler with routeRegistry and no code
reads corsHandler) or add a public getCorsHandler() getter that returns the same
instance for testing/debugging to keep parity with getRouteRegistry(); update
enableCors() to set the getter-backed value if you choose to keep it. Replace
direct console logging in warnOnce (and any console.log usage) to route through
a logger obtained from the route registry (e.g. call
this.routeRegistry.getLogger().warn(message) or similar) and keep the same flag
semantics (versioningWarningShown, errorHandlerWarningShown,
notFoundHandlerWarningShown) so the messages are still emitted only once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/compression-handler.ts`:
- Around line 210-213: The compression handler currently skips shouldCompress
when body is undefined, so streaming responses never run non-size checks
(Content-Encoding, Content-Type, custom filter); update the handler to always
call shouldCompress(req, res, body) for streaming responses and only use the
body length/threshold inside shouldCompress (or guard the threshold check when
body is undefined) so that encoding/type/filter checks run even without a
concrete body size; refer to the shouldCompress function and the compression
handler method that decides compression when body is omitted/streamed and ensure
shouldCompress gracefully handles a missing body by skipping size-only logic but
still applying other checks.
- Around line 143-177: The decompressRequest method can inflate small compressed
bodies into arbitrarily large Buffers; update decompressRequest (and associated
helpers gunzip, inflate, brotliDecompress) to enforce a configurable
inflated-size cap (e.g. this.options.maxInflatedBodySize) or switch to capped
streaming decompression (zlib streams with a byte-counter) so decompression
aborts and throws a clear error (or returns a 413-like error) when the inflated
size exceeds the limit; ensure the check is applied for 'gzip', 'deflate', and
'br' paths and surface the limit via options so callers can configure it.
- Around line 225-239: The branches in the compression-handler currently
overwrite any existing Vary header when setting Accept-Encoding; add a private
helper appendVaryAcceptEncoding(res: UwsResponse): void that reads existing vary
via res.getHeader('vary') (or this.getHeader(...) if present), splits on commas,
trims and lowercases entries, and only appends ", Accept-Encoding" if not
already present (preserving the original casing/value otherwise); replace the
direct res.setHeader('vary', 'Accept-Encoding') calls in the brotli/gzip/deflate
branches with calls to appendVaryAcceptEncoding so you no longer clobber
existing Vary values.

In `@src/platform/cors-handler.spec.ts`:
- Around line 443-456: The existing test comment incorrectly states the function
validator is invoked for a missing Origin; update the comment to accurately
state that isOriginAllowed short-circuits to true for null origins so the
provided origin function is not called (referencing isOriginAllowed and
CorsHandler), and clarify that setOriginHeader still uses the wildcard behavior
in setOriginHeader when no Origin header is present.

In `@src/platform/cors-handler.ts`:
- Around line 179-209: isOriginAllowed() can return false but handle() currently
falls through for OPTIONS preflight requests; update the handle() logic to
detect when isOriginAllowed(origin) === false and req.method === 'OPTIONS' and
immediately send a short deterministic response (e.g., res.statusCode = 403;
res.end() or a 204 if you prefer) so preflight requests do not fall through to
route handlers; keep returning false for non-OPTIONS requests so normal routing
is preserved. Reference: update the handle() function to check isOriginAllowed
and short-circuit OPTIONS responses.
- Around line 217-245: setOriginHeader currently emits
Access-Control-Allow-Origin: * when allowedOrigin is '*' or true even if
credentials are enabled and the request has no Origin, producing an invalid
wildcard+credentials combo; update setOriginHeader (the method named
setOriginHeader and the surrounding logic that reads this.options.credentials
and allowedOrigin) to detect the case where credentials are true
(this.options.credentials) and origin is falsy and avoid writing the wildcard:
instead do not emit '*' — either omit ACAO or emit the literal 'null' value for
ACAO (recommended) when origin is missing; ensure the Vary header logic and the
other branches for string/echoed origins remain unchanged.

In `@src/platform/file-worker-pool.ts`:
- Around line 158-167: selectWorker() can throw a cryptic TypeError when
this.workers is empty after terminate(); change selectWorker to check for an
empty this.workers and immediately throw a clear, fast-fail Error (e.g.,
"FileWorkerPool has been terminated" or "no available workers") instead of
allowing Array.prototype.reduce to run. Update any other selection helper used
at lines ~179-189 the same way so readFile() and similar callers receive a
meaningful error rather than the opaque "Reduce of empty array" exception;
reference the selectWorker, terminate, and readFile symbols to locate and apply
the check consistently.
- Around line 58-88: The FileWorker error/exit handlers must mark the worker as
dead and notify FileWorkerPool so the pool removes or replaces it: add an isDead
flag on FileWorker (set in the worker 'error' and non-zero 'exit' handlers) and
call a callback provided in FileWorker constructor (e.g., onTerminalFailure)
that lets FileWorkerPool remove this FileWorker from its workers array and
optionally spawn a replacement to preserve pool size; also update
FileWorker.readFile() to check isDead before posting (and immediately reject if
dead) and ensure FileWorkerPool.selectWorker()/isIdle logic ignores dead workers
(or filters workers list) so no dead worker is ever chosen for new tasks, and
keep rejecting and cleaning up pendingTaskKeys/workerTasks as currently done.

In `@src/platform/multipart-handler.spec.ts`:
- Around line 319-332: The test currently flips the private doneReadingData flag
but MultipartFormHandler.parse checks this.request.readableEnded, so change the
test to drive an actual end-of-stream (so readableEnded becomes true) before
calling req.multipart: for example, call (req as any).push(null) or emit('end')
on the mock request returned by setupMultipartRequest(boundary, 0) so that
MultipartFormHandler.parse observes readableEnded and returns early;
alternatively add a spy on MultipartFormHandler.parse to assert the early-exit
branch was taken when req.readableEnded is true. Use symbols req.multipart,
MultipartFormHandler.parse, readableEnded and doneReadingData to locate the
relevant code.

In `@src/platform/multipart-handler.ts`:
- Around line 115-119: The multipart content-type check in
MultipartFormHandler.parse() is stricter than UwsRequest.multipart(): it uses
const contentType = this.request.contentType and the regex
/^(multipart\/.+);(.*)$/i which rejects valid headers without parameters (e.g.,
"multipart/form-data"), causing parse() to silently return; change the check to
align with UwsRequest.multipart() by using a startsWith('multipart/') test on
contentType (or, alternatively, return a typed rejection indicating "invalid
multipart" so callers can distinguish non-multipart from malformed multipart),
and ensure references to this.request.contentType, UwsRequest.multipart(), and
MultipartFormHandler.parse() are updated accordingly so busboy can surface
boundary errors instead of silently skipping the handler.

In `@src/platform/route-registry.ts`:
- Around line 374-381: The 404 fallback path currently bypasses the CORS handler
causing preflight OPTIONS to get 404; modify the fallback logic in
route-registry.ts so that before sending the 404 response you invoke the
existing cors handler (this.corsHandler.handle(req, res)) and if it returns
true, return early (mirroring executeHandler's behavior). Alternatively, when
registerCorsHandler is called ensure a catch-all OPTIONS route is registered;
but the simplest fix is to call this.corsHandler.handle in the no-match/404
branch (the same place that currently sends the 404) and short-circuit if
handled.

In `@src/platform/static-file-handler.ts`:
- Around line 478-491: Update the misleading comments around the range handling
logic: change the comment on the `if (ranges === -1)` branch to state that `-1`
means "unsatisfiable" per range-parser semantics and update the comment on the
`if (ranges === -2)` branch (which calls `this.serveFullFile(res, filePath,
stat)`) to state that `-2` means "malformed" (syntactically invalid) rather than
unsatisfiable; leave the runtime behavior (sending 416 for `-1` and serving full
file for `-2`) unchanged.
- Around line 294-322: The encodeURI → decodeURIComponent roundtrip in
validateAndDecodePath corrupts already-encoded paths; remove the encodeURI call
and decode the incoming filePath directly. In the validateAndDecodePath method,
delete the block that conditionally does filePath = encodeURI(filePath) (the
check against this.options.skipEncodePath) and instead call
decodeURIComponent(filePath) inside the existing try/catch, preserving the null
byte check, UP_PATH_REGEXP traversal check, and existing response handling so
path resolution and startsWith safety checks remain unchanged.

In `@src/platform/uws-request.ts`:
- Around line 837-843: The code wrongly treats undefined this.contentLength as
“no body” and skips registering onData, which breaks chunked/Transfer-Encoding
uploads; in the request body handling (look for this.contentLength,
contentLength variable, doneReadingData and where onData is registered) only
short-circuit when contentLength === 0, not when undefined—ensure you register
the onData handler when contentLength is undefined (chunked) and preserve the
existing behavior when contentLength is explicitly 0 so doneReadingData can
remain true for empty bodies.
- Around line 845-849: When Content-Length > maxBodySize the code currently
calls this.uwsRes.close() but leaves doneReadingData true and no error, allowing
await req.body to resolve to an empty buffer; change the branch so it sets a
failure/abort state and rejects any pending body promise before closing the
socket. Specifically, when the check (contentLength > maxBodySize) triggers, set
a clear error flag (e.g., this._bodyError or this.aborted) and invoke the
existing body promise rejection path (or call the body reject handler) with a
PayloadTooLarge/Abort error so awaiting code (req.body) fails, then call
this.uwsRes.close().
- Around line 223-234: The uWS ArrayBuffer is being wrapped with
Buffer.from(chunk) in handleIncomingChunk, which shares the underlying memory
and leads to neutered/corrupted data after the uWS callback returns; replace
that shared view with a copied buffer (e.g., create a copy via new
Uint8Array(chunk) or ArrayBuffer.slice before constructing the Buffer) so the
code paths that use the resulting buffer (totalReceivedBytes incrementing and
any buffered/Readable.push consumers) hold an independent copy of the data.
- Around line 332-367: The _read() method can return without resuming uWS when
bodyParserMode is 'awaiting', so update the logic in _read() (function _read) to
resume the uWS stream for non-pipe consumers: when there are no bufferedChunks
and bodyParserMode is either 'streaming' OR 'awaiting' (the state used for
non-pipe consumers), call this.resumeStream(); keep the existing
bufferedChunks/doneReadingData handling and ensure you reference the
bufferedChunks, doneReadingData, bodyParserMode and resumeStream identifiers
when making this change.
- Around line 857-866: In the onAborted handler of UwsRequest (where you set
this.aborted, this.abortError and call this.destroy), avoid emitting an 'error'
when no error listeners are attached: check the instance's error listeners
(e.g., this.listenerCount('error') or this.listeners('error').length) and only
call this.destroy(this.abortError) when there is at least one 'error' listener;
otherwise call this.destroy() with no argument so the stream closes without
triggering an uncaught error. Ensure this change is colocated with the existing
onAborted block and that getAllData / json() / text() / buffer() behavior
remains unchanged.

---

Nitpick comments:
In `@src/platform/file-worker-pool.spec.ts`:
- Around line 126-162: Add a test that forces a worker to crash and verifies the
pool recovers: instantiate FileWorkerPool (use 1 or 2 workers), start a pending
readFile() task, then simulate a worker `error`/unexpected exit for that worker
(or kill the worker thread) to trigger the pool's crash handler, assert the
pending task is rejected or handled per pool semantics, then call readFile()
again and assert it resolves to the expected content to prove the dead worker
was replaced; reference FileWorkerPool, readFile, and terminate in the test and
ensure you await pool termination at the end.

In `@src/platform/file-worker-pool.ts`:
- Around line 12-32: The inline workerCode currently uses fs.readFileSync
without any size cap which can block worker threads on large files; update the
message handler inside workerCode (the parentPort.on('message', ... ) block) to
stat the file first (e.g., fs.statSync(message.path)) and reject files larger
than 768 * 1024 bytes by posting back an error { key: message.key, err: 'file
too large' }, otherwise perform the read (use a bounded read or continue with
readFileSync after the size check) and send the ArrayBuffer as before; ensure
the error path matches the existing postMessage shape so FileWorkerPool's
callers receive the error.

In `@src/platform/route-registry.ts`:
- Around line 842-852: Add a way to clear the registered CORS handler for test
isolation and dynamic reconfiguration by adding a public clearCorsHandler():
void method that sets the internal corsHandler to undefined and logs a
debug/info message; keep registerCorsHandler(handler: CorsHandler) as-is but
ensure the corsHandler field’s type allows undefined (e.g., CorsHandler |
undefined) so both registerCorsHandler and clearCorsHandler compile and behave
correctly — changes should reference the existing registerCorsHandler method and
the corsHandler property.

In `@src/platform/uws-platform.adapter.ts`:
- Around line 82-102: Remove or expose the unused corsHandler: either delete the
private corsHandler?: CorsHandler field (since enableCors() registers the
handler with routeRegistry and no code reads corsHandler) or add a public
getCorsHandler() getter that returns the same instance for testing/debugging to
keep parity with getRouteRegistry(); update enableCors() to set the
getter-backed value if you choose to keep it. Replace direct console logging in
warnOnce (and any console.log usage) to route through a logger obtained from the
route registry (e.g. call this.routeRegistry.getLogger().warn(message) or
similar) and keep the same flag semantics (versioningWarningShown,
errorHandlerWarningShown, notFoundHandlerWarningShown) so the messages are still
emitted only once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d9de5e5-5018-4c14-a35f-3e86672d0c03

📥 Commits

Reviewing files that changed from the base of the PR and between 01785ed and 4ab0092.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • package.json
  • src/interfaces/http-options.interface.ts
  • src/platform/compression-handler.spec.ts
  • src/platform/compression-handler.ts
  • src/platform/cors-handler.spec.ts
  • src/platform/cors-handler.ts
  • src/platform/file-worker-pool.spec.ts
  • src/platform/file-worker-pool.ts
  • src/platform/index.ts
  • src/platform/multipart-handler.spec.ts
  • src/platform/multipart-handler.ts
  • src/platform/route-registry.ts
  • src/platform/static-file-handler.spec.ts
  • src/platform/static-file-handler.ts
  • src/platform/uws-platform.adapter.spec.ts
  • src/platform/uws-platform.adapter.ts
  • src/platform/uws-request.spec.ts
  • src/platform/uws-request.ts

Comment thread src/platform/compression-handler.ts
Comment thread src/platform/compression-handler.ts Outdated
Comment thread src/platform/compression-handler.ts Outdated
Comment thread src/platform/cors-handler.spec.ts Outdated
Comment thread src/platform/cors-handler.ts Outdated
Comment thread src/platform/uws-request.ts
Comment thread src/platform/uws-request.ts
Comment thread src/platform/uws-request.ts Outdated
Comment thread src/platform/uws-request.ts Outdated
Comment thread src/platform/uws-request.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (2)
src/platform/route-registry.ts (1)

289-303: ⚠️ Potential issue | 🟠 Major

CORS is still bypassed for unmatched routes and unregistered preflights.

executeHandler() only runs after a route match, while this fallback sends 404 directly and registerCorsHandler() does not add an OPTIONS catch-all. Preflight requests to unregistered paths can still miss CORS handling.

Proposed fix
           // If no route matched, send 404
           if (!matched) {
+            const req = new UwsRequest(uwsReq, uwsRes, []);
             const res = new UwsResponse(uwsRes);
+
+            if (this.corsHandler && (await this.corsHandler.handle(req, res))) {
+              return;
+            }
 
             // Only send 404 if response hasn't been sent and isn't aborted
             // UwsResponse.send() already handles aborted state, but checking here
             // avoids unnecessary work and makes intent explicit
   registerCorsHandler(handler: import('./cors-handler').CorsHandler): void {
     if (!handler) {
       throw new Error('CORS handler cannot be null or undefined');
     }
     if (this.corsHandler) {
       this.logger.warn('CORS handler is being replaced. This may indicate a configuration issue.');
     }
     this.corsHandler = handler;
+
+    this.uwsApp.options('/*', async (uwsRes: uWS.HttpResponse, uwsReq: uWS.HttpRequest) => {
+      const req = new UwsRequest(uwsReq, uwsRes, []);
+      const res = new UwsResponse(uwsRes);
+
+      if (await handler.handle(req, res)) {
+        return;
+      }
+
+      if (!res.headersSent && !res.isAborted) {
+        res.status(404).send({
+          statusCode: 404,
+          message: 'Not Found',
+        });
+      }
+    });
   }

Also applies to: 847-855

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/route-registry.ts` around lines 289 - 303, The 404 fallback in
the unmatched-route block bypasses CORS because executeHandler() (which applies
registerCorsHandler()) only runs on matched routes; update the fallback that
constructs UwsResponse(uwsRes) (the block checking !matched) to invoke the same
CORS preflight handling before returning 404—i.e., call the module/function used
by registerCorsHandler() (or reuse the cors handling path used by
executeHandler()) for OPTIONS/unregistered paths and short-circuit only if the
CORS handler has already handled/terminated the response; ensure you reference
and reuse the same handler logic rather than duplicating behavior so
UwsResponse.headersSent/isAborted checks and res.status(404)/res.send(...)
remain executed only when CORS didn’t respond.
src/platform/compression-handler.ts (1)

485-496: ⚠️ Potential issue | 🟡 Minor

Match Vary entries by exact token.

The overwrite issue is fixed, but substring matching can treat X-Accept-Encoding as already varied. Also preserve Vary: * as-is.

🐛 Proposed fix
     if (existingVary) {
       const varyValue = typeof existingVary === 'string' ? existingVary : existingVary.join(', ');
-      // Only add Accept-Encoding if not already present (case-insensitive check)
-      if (!varyValue.toLowerCase().includes('accept-encoding')) {
+      const varyValues = varyValue
+        .split(',')
+        .map((value) => value.trim())
+        .filter(Boolean);
+
+      if (varyValues.includes('*')) {
+        return;
+      }
+
+      // Only add Accept-Encoding if not already present as a Vary token.
+      if (!varyValues.some((value) => value.toLowerCase() === 'accept-encoding')) {
         res.setHeader('vary', `${varyValue}, Accept-Encoding`);
       }
     } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/compression-handler.ts` around lines 485 - 496, The
appendVaryAcceptEncoding function currently uses substring matching which
incorrectly treats headers like "X-Accept-Encoding" as a match and may overwrite
Vary: *; update appendVaryAcceptEncoding to read the existingVary (string or
string[]), split it into comma-separated tokens, trim each token and perform a
case-insensitive exact token comparison against "accept-encoding"; if any token
is "*" leave the header unchanged; only append "Accept-Encoding" when no exact
"accept-encoding" token exists and reconstruct the header preserving original
token order and formatting when calling res.setHeader.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/compression-handler.ts`:
- Around line 158-179: The code currently treats Content-Encoding as a single
value in the method using this.getHeader and only decodes exact singletons;
change the logic in the compression handling (the block using contentEncoding,
encoding, and the switch in compression-handler.ts and the similar block at
lines 473-478) to parse the header as a comma-separated list (and handle array
header values from this.getHeader if returned), split on ',', trim each token,
then iterate the encodings in reverse order applying the appropriate decoder
functions (this.gunzip, this.inflate, this.brotliDecompress, or identity) to the
body for each coding; for unknown codings skip or stop gracefully so you don't
leave bytes compressed. Ensure you update both locations that perform the
single-switch logic.
- Around line 237-258: When enabling compression in createCompressionStream(),
remove any stale Content-Length by calling res.removeHeader('content-length')
immediately after setting the Content-Encoding header and before returning the
compression stream (ensure this change is applied in the branches handling 'br',
'gzip', and 'deflate'). In compressBuffer(), either remove the Content-Length
header from res (preferred for streaming semantics) or update it to the length
of the compressed buffer before returning the compressed result so the header
matches the actual payload size; locate these changes in the
createCompressionStream and compressBuffer functions and apply consistently for
all compression types.

In `@src/platform/cors-handler.spec.ts`:
- Around line 13-17: The test helper setupRequest currently treats any falsy
origin as "no header", so calling setupRequest('') removes the Origin header;
change the condition that builds mockReq.headers to only omit the header when
origin is strictly undefined (e.g., use origin !== undefined) so an explicit
empty string is preserved; update the identical helper usage(s) that construct
mockReq.headers (including the repeated block referenced around lines 504-522)
to use the same undefined check so tests for an empty Origin header actually
send an empty string header.

In `@src/platform/cors-handler.ts`:
- Around line 139-151: The preflight rejection branch in CorsHandler.handle()
sets access-control-* response headers earlier and then sends 403 (via
res.status(403).send()) while leaving those headers set, and an explicit
allowedHeaders: [] currently causes every preflight with
Access-Control-Request-Headers to be rejected; to fix: move or defer setting
access-control-allow-origin, -allow-credentials and -expose-headers until after
preflight header validation completes (i.e., perform the requested headers
validation block that uses allowedHeadersExplicitlySet and
options.allowedHeaders before writing those headers), or if you prefer minimal
change clear those headers (res.removeHeader or res.setHeader to '') right
before res.status(403).send(); also change the allowed-headers logic so that
when options.allowedHeaders is explicitly set to [] you either treat it as
"unset" (permissive echo) by checking this.options.allowedHeaders.length === 0
and falling back to echo behavior, or document/retain the strict semantics —
update CorsHandler.handle() and the allowedHeadersExplicitlySet handling
accordingly.
- Around line 185-217: The early return for a missing origin in isOriginAllowed
causes origin: false to be ignored and prevents the user-supplied validator from
ever receiving null; change the logic so you first inspect this.options.origin
(the allowedOrigin variable) and handle boolean and function branches before
short-circuiting on a null origin: specifically, if allowedOrigin is a boolean
return it (so false can deny no-origin requests), if it's a function call
allowedOrigin(origin) and await the result (passing origin which may be null),
then handle string/array branches and finally fall back to allowing when origin
is null and no other rule applied; ensure you reference the isOriginAllowed
method and this.options.origin when making the change.

In `@src/platform/static-file-handler.ts`:
- Around line 465-477: The If-Match branch in isPreconditionFailure currently
calls matchesETag (which permits weak matches) and must instead perform a
strict/strong comparison: when if-match is not "*" parseTokenList(ifMatchStr)
and check for any token that is a strong ETag (i.e., not prefixed with 'W/')
that equals the response etag exactly; do not use matchesETag here. If no exact
strong token match is found return true (precondition failure). Apply the same
change to the corresponding If-Match handling in the other block (around the
502-513 region) so all If-Match checks require strict equality and ignore weak
tokens.

In `@src/platform/uws-platform.adapter.ts`:
- Around line 737-760: The route callback is re-wrapping raw uWS objects into
UwsRequest/UwsResponse which breaks types and static serving; change the
this.get('/*', ...) registration to accept the already-wrapped (req, res)
signature provided by RouteRegistry (i.e., use (req: UwsRequest, res:
UwsResponse) => { await handler.serve(req, res, req.path); ... }) and remove new
UwsRequest/new UwsResponse creation, and also register a HEAD route for '/*'
(e.g., this.head('/*', same handler) or register both methods) so the handler's
HEAD branch is reachable; keep the existing error handling but operate on the
provided res methods/flags (headersSent/isAborted) rather than a re-created
response.

---

Duplicate comments:
In `@src/platform/compression-handler.ts`:
- Around line 485-496: The appendVaryAcceptEncoding function currently uses
substring matching which incorrectly treats headers like "X-Accept-Encoding" as
a match and may overwrite Vary: *; update appendVaryAcceptEncoding to read the
existingVary (string or string[]), split it into comma-separated tokens, trim
each token and perform a case-insensitive exact token comparison against
"accept-encoding"; if any token is "*" leave the header unchanged; only append
"Accept-Encoding" when no exact "accept-encoding" token exists and reconstruct
the header preserving original token order and formatting when calling
res.setHeader.

In `@src/platform/route-registry.ts`:
- Around line 289-303: The 404 fallback in the unmatched-route block bypasses
CORS because executeHandler() (which applies registerCorsHandler()) only runs on
matched routes; update the fallback that constructs UwsResponse(uwsRes) (the
block checking !matched) to invoke the same CORS preflight handling before
returning 404—i.e., call the module/function used by registerCorsHandler() (or
reuse the cors handling path used by executeHandler()) for OPTIONS/unregistered
paths and short-circuit only if the CORS handler has already handled/terminated
the response; ensure you reference and reuse the same handler logic rather than
duplicating behavior so UwsResponse.headersSent/isAborted checks and
res.status(404)/res.send(...) remain executed only when CORS didn’t respond.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 48926638-6cdb-4b87-b10f-559c0a37938a

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab0092 and e46dfee.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (20)
  • package.json
  • src/interfaces/http-options.interface.ts
  • src/interfaces/uws-options.interface.ts
  • src/platform/compression-handler.spec.ts
  • src/platform/compression-handler.ts
  • src/platform/cors-handler.spec.ts
  • src/platform/cors-handler.ts
  • src/platform/file-worker-pool.spec.ts
  • src/platform/file-worker-pool.ts
  • src/platform/index.ts
  • src/platform/multipart-handler.spec.ts
  • src/platform/multipart-handler.ts
  • src/platform/route-registry.spec.ts
  • src/platform/route-registry.ts
  • src/platform/static-file-handler.spec.ts
  • src/platform/static-file-handler.ts
  • src/platform/uws-platform.adapter.spec.ts
  • src/platform/uws-platform.adapter.ts
  • src/platform/uws-request.spec.ts
  • src/platform/uws-request.ts
✅ Files skipped from review due to trivial changes (3)
  • package.json
  • src/platform/index.ts
  • src/platform/uws-request.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/platform/uws-platform.adapter.spec.ts
  • src/platform/multipart-handler.spec.ts
  • src/platform/file-worker-pool.spec.ts
  • src/platform/multipart-handler.ts
  • src/platform/file-worker-pool.ts
  • src/platform/compression-handler.spec.ts
  • src/platform/uws-request.spec.ts

Comment thread src/platform/compression-handler.ts
Comment thread src/platform/compression-handler.ts
Comment thread src/platform/cors-handler.spec.ts
Comment thread src/platform/cors-handler.ts
Comment thread src/platform/cors-handler.ts
Comment thread src/platform/static-file-handler.ts Outdated
Comment thread src/platform/uws-platform.adapter.ts Outdated
…, compression)

- Add multipart/form-data support with streaming file uploads
- Implement static file serving with range requests and caching
- Add CORS handler with preflight and origin validation
- Implement request/response compression (gzip/deflate/brotli)
- Add hybrid readable stream with lazy activation and backpressure
- Implement file worker pool for optimized static file serving
@VikramAditya33 VikramAditya33 merged commit 2627f74 into v2-http-support Apr 23, 2026
3 checks passed
@VikramAditya33 VikramAditya33 deleted the v2-p6 branch April 23, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use Streaming compression instead of synchronous compression Make UwsRequest extend Readable for multipart/form-data support

1 participant