Skip to content

Conversation

@ardatan
Copy link
Owner

@ardatan ardatan commented Feb 20, 2025

Alternative to #2074
Closes #2076
Closes #2074
Completes WHATWG-63

Keeps the optimization logic

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of "set-cookie" headers, ensuring that multiple values are processed correctly for enhanced header management.
  • Tests
    • Introduced new tests to validate the correct concatenation of cookie values and the accurate retrieval of individual "set-cookie" values.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2025

Walkthrough

The pull request addresses a bug in the @whatwg-node/node-fetch package related to the handling of set-cookies in headers. It modifies the _setCookies property in the PonyfillHeaders class to be optional and updates various methods to accommodate this change. Additionally, a new test suite is introduced to ensure that the Set-Cookie header values are processed correctly.

Changes

File(s) Change Summary
.changeset/curvy-insects-cheer.md Added a patch summary for the bug fix that prevents set-cookies from being ignored.
packages/node-fetch/src/Headers.ts Modified the PonyfillHeaders class: _setCookies is now optional; added optional chaining for checks; updated logic to initialize _setCookies when appending values; ensured the get method returns string values consistently; no public API changes.
packages/node-fetch/tests/Headers.spec.ts Introduced a new test suite for handling Set-Cookie headers, verifying that cookie values are correctly concatenated into a single string, such as transforming an array into 'a=b,c=d'.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PonyfillHeaders
    Client->>PonyfillHeaders: set("set-cookie", value)
    Note over PonyfillHeaders: Initialize _setCookies if undefined
    PonyfillHeaders-->>PonyfillHeaders: Append cookie value to _setCookies
    Client->>PonyfillHeaders: get("set-cookie")
    PonyfillHeaders-->>Client: Return concatenated cookie string
Loading

Possibly related PRs

Poem

I'm a rabbit hopping through the code,
Setting cookies on a merry road.
No longer lost, each header’s neat,
With tests ensuring all's complete.
Hop along, dear coder, let fixes delight—
In our code garden, everything's just right! 🐰✨

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@whatwg-node/node-fetch 0.7.10-alpha-20250220113249-0583b3e53c27f1e5d23782766f8ae80637457e0b npm ↗︎ unpkg ↗︎

@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2025

@benchmarks/node-fetch results (noConsumeBody)

   ✓ active_handles.................: avg=140.679579 min=16     med=141     max=183     p(90)=158     p(95)=162    
     data_received..................: 21 MB  691 kB/s
     data_sent......................: 13 MB  447 kB/s
     http_req_blocked...............: avg=2.36µs     min=611ns  med=1.38µs  max=2.72ms  p(90)=2.06µs  p(95)=2.4µs  
     http_req_connecting............: avg=514ns      min=0s     med=0s      max=2.09ms  p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=22.08ms    min=2.96ms med=21.38ms max=1.14s   p(90)=27.65ms p(95)=29.88ms
       { expected_response:true }...: avg=22.08ms    min=2.96ms med=21.38ms max=1.14s   p(90)=27.65ms p(95)=29.88ms
     http_req_failed................: 0.00%  ✓ 0           ✗ 135424
     http_req_receiving.............: avg=36.12µs    min=9.1µs  med=23.74µs max=20.14ms p(90)=39.1µs  p(95)=45.68µs
     http_req_sending...............: avg=11.29µs    min=3.21µs med=6.63µs  max=13.71ms p(90)=10.22µs p(95)=13.83µs
     http_req_tls_handshaking.......: avg=0s         min=0s     med=0s      max=0s      p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=22.03ms    min=2.52ms med=21.34ms max=1.14s   p(90)=27.6ms  p(95)=29.78ms
     http_reqs......................: 135424 4513.490631/s
     iteration_duration.............: avg=44.27ms    min=9.69ms med=42.59ms max=1.17s   p(90)=49.47ms p(95)=54.47ms
     iterations.....................: 67686  2255.878772/s
     vus............................: 100    min=100       max=100 
     vus_max........................: 100    min=100       max=100 

@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2025

@benchmarks/node-fetch results (consumeBody)

   ✓ active_handles.................: avg=139.565403 min=12      med=139     max=189    p(90)=163     p(95)=167    
     data_received..................: 20 MB  668 kB/s
     data_sent......................: 13 MB  428 kB/s
     http_req_blocked...............: avg=2.4µs      min=651ns   med=1.25µs  max=3.1ms  p(90)=2.02µs  p(95)=2.23µs 
     http_req_connecting............: avg=681ns      min=0s      med=0s      max=3.08ms p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=22.85ms    min=3.36ms  med=22.16ms max=1.17s  p(90)=28.62ms p(95)=30.13ms
       { expected_response:true }...: avg=22.85ms    min=3.36ms  med=22.16ms max=1.17s  p(90)=28.62ms p(95)=30.13ms
     http_req_failed................: 0.00%  ✓ 0           ✗ 130922
     http_req_receiving.............: avg=32.48µs    min=8.83µs  med=23.37µs max=15.7ms p(90)=37.22µs p(95)=43.15µs
     http_req_sending...............: avg=10.27µs    min=3.19µs  med=5.66µs  max=9.16ms p(90)=9.37µs  p(95)=12.54µs
     http_req_tls_handshaking.......: avg=0s         min=0s      med=0s      max=0s     p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=22.8ms     min=3.31ms  med=22.12ms max=1.17s  p(90)=28.58ms p(95)=30.08ms
     http_reqs......................: 130922 4363.656566/s
     iteration_duration.............: avg=45.8ms     min=10.49ms med=44.15ms max=1.2s   p(90)=49.35ms p(95)=54.96ms
     iterations.....................: 65440  2181.128349/s
     vus............................: 100    min=100       max=100 
     vus_max........................: 100    min=100       max=100 

Copy link

@louy louy left a comment

Choose a reason for hiding this comment

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

Nice one!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/node-fetch/tests/Headers.spec.ts (1)

64-73: Enhance test coverage for Set-Cookie handling.

While the current test case is good, consider adding more test cases to cover:

  • Empty arrays
  • Single value arrays
  • Null/undefined values
  • Verification that the original array isn't modified

Here's an example of additional test cases:

 describe('Set-Cookie', () => {
   it('handles values in the given map', () => {
     const init = {
       // Record<string, string[]> is not a HeadersInit actually
       'set-cookie': ['a=b', 'c=d'],
     } as any;
     const headers = new PonyfillHeaders(init);
     expect(headers.get('Set-Cookie')).toBe('a=b,c=d');
   });
+  it('handles empty arrays', () => {
+    const init = { 'set-cookie': [] } as any;
+    const headers = new PonyfillHeaders(init);
+    expect(headers.get('Set-Cookie')).toBeNull();
+  });
+  it('handles single value arrays', () => {
+    const init = { 'set-cookie': ['a=b'] } as any;
+    const headers = new PonyfillHeaders(init);
+    expect(headers.get('Set-Cookie')).toBe('a=b');
+  });
+  it('preserves the original array', () => {
+    const cookieArray = ['a=b', 'c=d'];
+    const init = { 'set-cookie': cookieArray } as any;
+    const headers = new PonyfillHeaders(init);
+    headers.append('set-cookie', 'e=f');
+    expect(cookieArray).toEqual(['a=b', 'c=d']);
+  });
 });
packages/node-fetch/src/Headers.ts (3)

121-129: Handle null values more gracefully in get method.

The toString() call could throw if value is null. Consider adding a null check.

 get(name: string): string | null {
   const value = this._get(name);

   if (value == null) {
     return null;
   }

-  return value.toString();
+  return value?.toString() ?? null;
 }

69-73: Reduce code duplication in set-cookie initialization.

The pattern this._setCookies ||= []; this._setCookies.push(value); is repeated in multiple places. Consider extracting it to a private method.

+private ensureSetCookies(value: string) {
+  this._setCookies ||= [];
+  this._setCookies.push(value);
+}

 // Then replace the repeated pattern with:
-this._setCookies ||= [];
-this._setCookies.push(value);
+this.ensureSetCookies(value);

Also applies to: 79-83, 92-96


131-136: Simplify the has method.

The has method could be simplified by leveraging the _get method which already handles null checks.

 has(name: string): boolean {
-  if (name === 'set-cookie') {
-    return !!this._setCookies?.length;
-  }
-  return !!this._get(name); // we might need to check if header exists and not just check if it's not nullable
+  return this._get(name) !== null;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14cd02a and a3545bc.

📒 Files selected for processing (3)
  • .changeset/curvy-insects-cheer.md (1 hunks)
  • packages/node-fetch/src/Headers.ts (12 hunks)
  • packages/node-fetch/tests/Headers.spec.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (21)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 22
  • GitHub Check: unit / node 20
  • GitHub Check: unit / bun
  • GitHub Check: alpha / snapshot
  • GitHub Check: unit / node 18
  • GitHub Check: server (undici)
  • GitHub Check: type check
  • GitHub Check: prettier
  • GitHub Check: server (ponyfill)
  • GitHub Check: e2e / cloudflare-modules
  • GitHub Check: node-fetch (consumeBody)
  • GitHub Check: e2e / cloudflare-workers
  • GitHub Check: lint
  • GitHub Check: esm
  • GitHub Check: server (native)
  • GitHub Check: e2e / azure-function
  • GitHub Check: e2e / aws-lambda
  • GitHub Check: node-fetch (noConsumeBody)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
.changeset/curvy-insects-cheer.md (1)

1-6: LGTM!

The changeset correctly describes the bug fix and uses the appropriate patch version bump.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2025

@benchmarks/server results (undici)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 219974      ✗ 0     
     data_received..................: 22 MB   737 kB/s
     data_sent......................: 8.8 MB  293 kB/s
     http_req_blocked...............: avg=1.36µs   min=861ns    med=1.14µs   max=291.19µs p(90)=1.84µs   p(95)=1.99µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=113.67µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=210.11µs min=152.99µs med=199.16µs max=56.76ms  p(90)=224.29µs p(95)=233.04µs
       { expected_response:true }...: avg=210.11µs min=152.99µs med=199.16µs max=56.76ms  p(90)=224.29µs p(95)=233.04µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 109987
     http_req_receiving.............: avg=25.19µs  min=13.71µs  med=23.6µs   max=2.51ms   p(90)=30.71µs  p(95)=33.4µs  
     http_req_sending...............: avg=6.41µs   min=4.05µs   med=5.57µs   max=145.43µs p(90)=8.19µs   p(95)=8.88µs  
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=178.49µs min=126.49µs med=167.62µs max=56.67ms  p(90)=189.67µs p(95)=197.86µs
     http_reqs......................: 109987  3666.105999/s
     iteration_duration.............: avg=268.27µs min=195.03µs med=256.31µs max=56.91ms  p(90)=284.91µs p(95)=295.85µs
     iterations.....................: 109987  3666.105999/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2025

@benchmarks/server results (native)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 230992      ✗ 0     
     data_received..................: 23 MB   774 kB/s
     data_sent......................: 9.2 MB  308 kB/s
     http_req_blocked...............: avg=1.45µs   min=891ns    med=1.23µs   max=202.44µs p(90)=1.95µs   p(95)=2.14µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=129.02µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=197.63µs min=141.43µs med=187.35µs max=52.78ms  p(90)=213.69µs p(95)=223.92µs
       { expected_response:true }...: avg=197.63µs min=141.43µs med=187.35µs max=52.78ms  p(90)=213.69µs p(95)=223.92µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 115496
     http_req_receiving.............: avg=25.62µs  min=12.96µs  med=24.04µs  max=4.41ms   p(90)=31.24µs  p(95)=34.05µs 
     http_req_sending...............: avg=6.46µs   min=4.02µs   med=5.58µs   max=3.09ms   p(90)=8.26µs   p(95)=9.12µs  
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=165.53µs min=118.97µs med=155.28µs max=52.69ms  p(90)=178.55µs p(95)=187.87µs
     http_reqs......................: 115496  3849.740626/s
     iteration_duration.............: avg=255.3µs  min=184.27µs med=244.21µs max=52.92ms  p(90)=274.28µs p(95)=287.45µs
     iterations.....................: 115496  3849.740626/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2025

@benchmarks/server results (ponyfill)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 295576      ✗ 0     
     data_received..................: 29 MB   970 kB/s
     data_sent......................: 12 MB   394 kB/s
     http_req_blocked...............: avg=1.38µs   min=862ns   med=1.18µs   max=196.7µs  p(90)=1.86µs   p(95)=2.04µs  
     http_req_connecting............: avg=0ns      min=0s      med=0s       max=132.86µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=140.24µs min=95.73µs med=135.57µs max=5.76ms   p(90)=158.07µs p(95)=165.2µs 
       { expected_response:true }...: avg=140.24µs min=95.73µs med=135.57µs max=5.76ms   p(90)=158.07µs p(95)=165.2µs 
     http_req_failed................: 0.00%   ✓ 0           ✗ 147788
     http_req_receiving.............: avg=24.37µs  min=12.32µs med=22.78µs  max=3.02ms   p(90)=30.33µs  p(95)=33.13µs 
     http_req_sending...............: avg=6.33µs   min=4.03µs  med=5.46µs   max=1.79ms   p(90)=8.06µs   p(95)=8.91µs  
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=109.53µs min=68.06µs med=104.5µs  max=5.7ms    p(90)=123.69µs p(95)=129.68µs
     http_reqs......................: 147788  4926.095107/s
     iteration_duration.............: avg=198.51µs min=141.7µs med=193.14µs max=6.16ms   p(90)=219.1µs  p(95)=228.24µs
     iterations.....................: 147788  4926.095107/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (1)
packages/node-fetch/src/Headers.ts (1)

130-138: ⚠️ Potential issue

Add null check before toString() call.

The toString() call might throw if value is null, despite the null check. Consider moving the toString() call inside the null check.

-    if (value == null) {
-      return null;
-    }
-
-    return value.toString();
+    return value?.toString() ?? null;
🧹 Nitpick comments (2)
packages/node-fetch/src/Headers.ts (2)

165-192: Consider early return after Set-Cookie iteration.

The code could be more readable with an early return after iterating through Set-Cookie headers.

-    this._setCookies?.forEach(setCookie => {
-      callback(setCookie, 'set-cookie', this);
-    });
+    if (this._setCookies?.length) {
+      this._setCookies.forEach(setCookie => {
+        callback(setCookie, 'set-cookie', this);
+      });
+      if (!this._map && !this.headersInit) {
+        return;
+      }
+    }

269-274: Replace non-null assertion with default empty array.

The non-null assertion operator could be replaced with a default empty array for better type safety.

-    return this._setCookies!;
+    return this._setCookies ?? [];
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3545bc and 0583b3e.

📒 Files selected for processing (2)
  • packages/node-fetch/src/Headers.ts (12 hunks)
  • packages/node-fetch/tests/Headers.spec.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (19)
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 22
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 20
  • GitHub Check: unit / bun
  • GitHub Check: server (undici)
  • GitHub Check: unit / node 18
  • GitHub Check: alpha / snapshot
  • GitHub Check: server (ponyfill)
  • GitHub Check: e2e / cloudflare-modules
  • GitHub Check: server (native)
  • GitHub Check: e2e / cloudflare-workers
  • GitHub Check: node-fetch (consumeBody)
  • GitHub Check: esm
  • GitHub Check: node-fetch (noConsumeBody)
  • GitHub Check: e2e / azure-function
  • GitHub Check: e2e / aws-lambda
  • GitHub Check: lint
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
packages/node-fetch/tests/Headers.spec.ts (1)

64-79: LGTM! Comprehensive test coverage for Set-Cookie handling.

The test suite effectively verifies both the concatenated string format via get and the array format via getSetCookie, ensuring proper handling of multiple Set-Cookie headers.

packages/node-fetch/src/Headers.ts (4)

71-116: LGTM! Maintains lazy initialization pattern.

The changes correctly handle Set-Cookie headers while preserving the performance optimization of lazy map initialization.


147-163: LGTM! Consistent handling of Set-Cookie headers.

The set and delete methods correctly handle Set-Cookie headers by managing the _setCookies array.


194-267: LGTM! Consistent iterator implementation.

The iterator methods correctly handle Set-Cookie headers while maintaining the existing optimization patterns.


14-14:

Details

❓ Verification inconclusive

Verify the impact of array filtering changes on other headers.

The array filtering logic in _get method now joins all multiple header values with commas. While this works for Set-Cookie headers, verify that this behavior is appropriate for other header types that might have multiple values.

Also applies to: 19-45


🏁 Script executed:

#!/bin/bash
# Search for header usage in tests to verify if any other headers use multiple values
rg -l "headers\.append\(" | xargs rg "append\([^)]+\)" -A 2

Length of output: 1048


Review Header Array Filtering Behavior Impact

Based on the test output, joining multiple values with commas works as expected for standard headers (e.g., the test for “x-HEADER” returns "foo, bar"). It appears that the changes to make _setCookies optional and update _get (including the filtering logic) correctly separate the handling of Set-Cookie headers from others.

  • Confirm that all non-cookie headers are intended to merge multiple values into a comma-separated string.
  • Ensure that headers requiring distinct treatment (like Set-Cookie) are not inadvertently joined as with other headers.

Please verify that these behaviors remain consistent with HTTP standards and that no unintended side effects occur for headers not covered by the current tests.

@ardatan ardatan merged commit 090b4b0 into master Feb 20, 2025
25 checks passed
@ardatan ardatan deleted the headers-init-set-cookies branch May 12, 2025 16:19
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.

Reading set-cookie header in node-fetch response returns default value

3 participants