Skip to content

perf(cookie): avoid quadratic chunked cookie parsing and header rebuilds#1368

Open
trivikr wants to merge 2 commits into
h3js:mainfrom
trivikr:chunked-cookie-handling
Open

perf(cookie): avoid quadratic chunked cookie parsing and header rebuilds#1368
trivikr wants to merge 2 commits into
h3js:mainfrom
trivikr:chunked-cookie-handling

Conversation

@trivikr

@trivikr trivikr commented Apr 16, 2026

Copy link
Copy Markdown

Summary

This PR improves cookie performance in the chunked-cookie path used by sessions.

Previously, setCookie() rebuilt and reparsed the full set-cookie header list on every write. Since setChunkedCookie() calls setCookie() for the main cookie and for each chunk, large chunked session writes became quadratic in chunk count. The read path had a similar issue: getChunkedCookie() repeatedly called getCookie(), which reparsed the full request cookie header each time.

What Changed

  • Cache parsed request cookies per Headers instance in src/utils/cookie.ts
  • Update getChunkedCookie() to read from a single parsed cookie map instead of reparsing per chunk.
  • Keep response-side set-cookie state in memory so unique cookie writes can append without reparsing existing headers.
  • Preserve deduplication behavior for overwrites by rebuilding headers only when the same logical cookie key is replaced.

Summary by CodeRabbit

  • Refactor

    • Improved cookie handling performance via caching to avoid repeated parsing and to update response cookie headers incrementally.
    • More efficient handling of chunked cookies by reading parsed cookie data once and minimizing header rewrites.
  • Tests

    • Added unit tests that verify single-parse reads for chunked cookies and that appending cookie chunks updates headers efficiently without unnecessary parsing.

Assisted-by: OpenAI:gpt-5.4
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
@trivikr trivikr requested a review from pi0 as a code owner April 16, 2026 14:09
@coderabbitai

coderabbitai Bot commented Apr 16, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Introduces WeakMap-backed caching for parsed request cookies and response Set-Cookie state in src/utils/cookie.ts, refactors setCookie to use cached state and incremental updates, updates getChunkedCookie to avoid repeated parsing, and adds a performance test test/unit/cookie-performance.test.ts.

Changes

Cohort / File(s) Summary
Cookie utility caching & refactor
src/utils/cookie.ts
Adds WeakMap caches for parsed request cookie maps and response Set-Cookie state; introduces helpers _getSetCookieState and _writeSetCookieState; changes parseCookies, setCookie, and getChunkedCookie to use cached state and avoid repeated parsing/deduplication.
Cookie performance tests
test/unit/cookie-performance.test.ts
New Vitest unit test that spies on cookie-es (parseCookie, parseSetCookie) to assert getChunkedCookie parses once and setChunkedCookie appends without calling parseSetCookie.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • pi0

Poem

🐇 I nibble crumbs of header lore,

I stash each cookie, one or more,
WeakMaps keep the crumbs in sight,
Parse once, hop — then everything's light! 🍪✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main performance improvements: avoiding quadratic parsing and header rebuilds in chunked cookie handling, which is the core objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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.

🧹 Nitpick comments (1)
src/utils/cookie.ts (1)

313-321: Malformed cookie handling is reasonable but worth documenting.

When parseSetCookie returns undefined for a malformed cookie (Line 316), the cookie is preserved in state.cookies but its key is undefined, meaning it won't match for deduplication. This is a reasonable fallback—malformed cookies won't be lost, but repeated writes of the same malformed cookie would accumulate. Consider adding a brief comment explaining this behavior.

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

In `@src/utils/cookie.ts` around lines 313 - 321, The code intentionally preserves
malformed Set-Cookie strings in state.cookies while pushing undefined into
state.keys when parseSetCookie returns undefined, which prevents them from being
deduplicated via state.distinctKeys; add a concise inline comment next to the
loop (or above the parseSetCookie usage) explaining that malformed cookies are
retained unchanged, that _getDistinctCookieKey is only called for parsed
cookies, and that resulting undefined keys mean malformed cookies will not be
deduplicated (so repeated malformed cookies may accumulate), to make this
fallback behavior explicit for future readers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/cookie.ts`:
- Around line 313-321: The code intentionally preserves malformed Set-Cookie
strings in state.cookies while pushing undefined into state.keys when
parseSetCookie returns undefined, which prevents them from being deduplicated
via state.distinctKeys; add a concise inline comment next to the loop (or above
the parseSetCookie usage) explaining that malformed cookies are retained
unchanged, that _getDistinctCookieKey is only called for parsed cookies, and
that resulting undefined keys mean malformed cookies will not be deduplicated
(so repeated malformed cookies may accumulate), to make this fallback behavior
explicit for future readers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3dcf89a2-5780-4843-9557-295def9802b7

📥 Commits

Reviewing files that changed from the base of the PR and between e2f8cbd and 03665bb.

📒 Files selected for processing (2)
  • src/utils/cookie.ts
  • test/unit/cookie-performance.test.ts

@pkg-pr-new

pkg-pr-new Bot commented Apr 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/h3@1368

commit: 03665bb

@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: 1

🧹 Nitpick comments (1)
src/utils/cookie.ts (1)

297-345: Consider extracting _getSetCookieState / _writeSetCookieState to an internal module.

This file is now well beyond the short-file target; moving this helper block to src/utils/internal/ would keep cookie.ts easier to maintain.

As per coding guidelines, "Keep files short — aim for less than 200 lines of code per file, split when larger."

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

In `@src/utils/cookie.ts` around lines 297 - 345, Extract the helper functions
_getSetCookieState and _writeSetCookieState into a new internal module under
src/utils/internal (e.g., src/utils/internal/setCookieState.ts), move any
related types/consts they need (SetCookieState, responseCookiesCache) or
re-export them as needed, and update all callers in cookie.ts to import these
functions from the new module; ensure references to parseSetCookie,
_getDistinctCookieKey, and Headers remain accessible (either by importing them
into the new module or keeping them in cookie.ts and passing them in), run/fix
tests and exports so behavior is identical after the move.
🤖 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/utils/cookie.ts`:
- Around line 34-44: The parseCookies function currently returns the
cached.cookies object instance directly from requestCookiesCache, allowing
callers to mutate shared state; change parseCookies (and the cache set) to
always return a shallow cloned cookies object instead of the original
reference—when reading cached (cached.source and cached.cookies) or after
computing cookies via parseCookie(source), return a new object (e.g.,
{...cached.cookies} or a new object copy of parseCookie result) so callers
cannot mutate the cache stored in requestCookiesCache; keep using the same keys
(headers, source) and function names (parseCookies, parseCookie,
requestCookiesCache) but ensure clones are stored/returned rather than the
original object references.

---

Nitpick comments:
In `@src/utils/cookie.ts`:
- Around line 297-345: Extract the helper functions _getSetCookieState and
_writeSetCookieState into a new internal module under src/utils/internal (e.g.,
src/utils/internal/setCookieState.ts), move any related types/consts they need
(SetCookieState, responseCookiesCache) or re-export them as needed, and update
all callers in cookie.ts to import these functions from the new module; ensure
references to parseSetCookie, _getDistinctCookieKey, and Headers remain
accessible (either by importing them into the new module or keeping them in
cookie.ts and passing them in), run/fix tests and exports so behavior is
identical after the move.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4fc46945-ade0-45f8-8171-adbbe74d3ede

📥 Commits

Reviewing files that changed from the base of the PR and between 03665bb and 1b53a6f.

📒 Files selected for processing (1)
  • src/utils/cookie.ts

Comment thread src/utils/cookie.ts
Comment on lines 34 to +44
export function parseCookies(event: HTTPEvent): Record<string, string | undefined> {
return parseCookie(event.req.headers.get("cookie") || "");
const headers = event.req.headers;
const source = headers.get("cookie") || "";
const cached = requestCookiesCache.get(headers);
if (cached && cached.source === source) {
return cached.cookies;
}

const cookies = parseCookie(source);
requestCookiesCache.set(headers, { source, cookies });
return cookies;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent shared mutable state from leaking through parseCookies.

At Line 39 and Line 44, the same cached object instance is returned. If any caller mutates that object, later reads for the same Headers instance can return corrupted cookie values.

💡 Proposed fix
 const requestCookiesCache = new WeakMap<
   Headers,
-  { source: string; cookies: Record<string, string | undefined> }
+  { source: string; cookies: Readonly<Record<string, string | undefined>> }
 >();

 export function parseCookies(event: HTTPEvent): Record<string, string | undefined> {
   const headers = event.req.headers;
   const source = headers.get("cookie") || "";
   const cached = requestCookiesCache.get(headers);
   if (cached && cached.source === source) {
-    return cached.cookies;
+    return cached.cookies as Record<string, string | undefined>;
   }

-  const cookies = parseCookie(source);
+  const cookies = Object.freeze(
+    parseCookie(source),
+  ) as Readonly<Record<string, string | undefined>>;
   requestCookiesCache.set(headers, { source, cookies });
-  return cookies;
+  return cookies as Record<string, string | undefined>;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/cookie.ts` around lines 34 - 44, The parseCookies function
currently returns the cached.cookies object instance directly from
requestCookiesCache, allowing callers to mutate shared state; change
parseCookies (and the cache set) to always return a shallow cloned cookies
object instead of the original reference—when reading cached (cached.source and
cached.cookies) or after computing cookies via parseCookie(source), return a new
object (e.g., {...cached.cookies} or a new object copy of parseCookie result) so
callers cannot mutate the cache stored in requestCookiesCache; keep using the
same keys (headers, source) and function names (parseCookies, parseCookie,
requestCookiesCache) but ensure clones are stored/returned rather than the
original object references.

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.

2 participants