Skip to content

v2: perf(request): optimize newHeadersFromIncoming and signal fast-path#332

Merged
yusukebe merged 5 commits intohonojs:v2from
GavinMeierSonos:perf/request-optimizations
Apr 7, 2026
Merged

v2: perf(request): optimize newHeadersFromIncoming and signal fast-path#332
yusukebe merged 5 commits intohonojs:v2from
GavinMeierSonos:perf/request-optimizations

Conversation

@GavinMeierSonos
Copy link
Copy Markdown

Summary

  • newHeadersFromIncoming: use direct array indexing with cached length instead of computed-property destructuring, avoiding intermediate object allocation on every header pair
  • signal property: bypass getRequestCache() (full Request construction) for known HTTP methods, calling getAbortController().signal directly. Unknown/forbidden methods still fall through to getRequestCache() which preserves the throwing behaviour for invalid method names

Changes

  • src/request.ts

- newHeadersFromIncoming: use direct array indexing with cached length
  instead of computed-property destructuring, avoiding intermediate object
  allocation on every header pair.

- signal property: bypass getRequestCache() (full Request construction)
  for known HTTP methods, calling getAbortController().signal directly.
  Unknown/forbidden methods still fall through to getRequestCache() which
  preserves the throwing behaviour for invalid method names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@usualoma
Copy link
Copy Markdown
Member

usualoma commented Apr 3, 2026

Hi @GavinMeierSonos,
Thank you creating pull request

newHeadersFromIncoming

This will certainly result in a slight but definite improvement.

signal

Since signal is almost always used for long-running requests, it may seem unnecessary for performance improvements; however, semantically, this[getAbortController]().signal is the correct approach.

Also, since there is no need to verify that it is a known method, would it be okay to implement it as follows, including changes to the test expectations?

diff --git i/src/request.ts w/src/request.ts
index ddac6c8..dbc4113 100644
--- i/src/request.ts
+++ w/src/request.ts
@@ -465,27 +465,11 @@ const requestPrototype: Record<string | symbol, any> = {
     }
     return false
   },
-}
-Object.defineProperty(requestPrototype, 'signal', {
-  get() {
-    // Fast path: known-valid methods don't need full Request construction.
-    switch (this[methodKey] as string) {
-      case 'GET':
-      case 'HEAD':
-      case 'POST':
-      case 'PUT':
-      case 'DELETE':
-      case 'OPTIONS':
-      case 'PATCH':
-      case 'TRACE':
-        return this[getAbortController]().signal
-      default:
-        // Unknown/forbidden methods fall through to getRequestCache() which throws
-        // for methods like 'connect', 'track', non-uppercase 'trace', etc.
-        return this[getRequestCache]().signal
-    }
+
+  get signal() {
+    return this[getAbortController]().signal
   },
-})
+}
 ;[
   'cache',
   'credentials',
diff --git i/test/request.test.ts w/test/request.test.ts
index 03f3c56..bc3e18d 100644
--- i/test/request.test.ts
+++ w/test/request.test.ts
@@ -617,11 +617,12 @@ describe('Request', () => {
       }
 
       for (const method of ['trace', 'TrAcE']) {
-        const reqBeforeSignal = createTraceLikeRequest(method)
-        await expect(reqBeforeSignal.text()).rejects.toBeInstanceOf(TypeError)
+        const req = createTraceLikeRequest(method)
+        await expect(req.text()).rejects.toBeInstanceOf(TypeError)
 
         const reqAfterSignal = createTraceLikeRequest(method)
-        expect(() => reqAfterSignal.signal).toThrow(/HTTP method is unsupported/)
+        expect(() => reqAfterSignal.signal).not.toThrow()
+        await expect(reqAfterSignal.text()).rejects.toBeInstanceOf(TypeError)
       }
     })

@usualoma
Copy link
Copy Markdown
Member

usualoma commented Apr 3, 2026

As a reviewer, I’d also like you to understand that, aside from splitting pull requests, having commits split appropriately makes the review process much less burdensome.

@GavinMeierSonos
Copy link
Copy Markdown
Author

@usualoma yeah I can make that happen. Thank you for the review.

@yusukebe yusukebe added the v2 label Apr 5, 2026
Copy link
Copy Markdown
Member

@yusukebe yusukebe left a comment

Choose a reason for hiding this comment

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

LGTM!

@yusukebe
Copy link
Copy Markdown
Member

yusukebe commented Apr 7, 2026

@usualoma I fixed the format error. Can you review this again?

@usualoma
Copy link
Copy Markdown
Member

usualoma commented Apr 7, 2026

I think we can merge it!

@yusukebe yusukebe merged commit 7503265 into honojs:v2 Apr 7, 2026
5 checks passed
@GavinMeierSonos
Copy link
Copy Markdown
Author

@usualoma @yusukebe Awesome 🚀

Thank you! I will take a look at the comments on the other PR as well and address those!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants