feat(request): return new Request with cached body in raw getter
This PR will fix the problem that we wanted to resolve with #4382.
Problem
The following code will cause the error:
import { Hono } from 'hono'
const app = new Hono()
app.post('/', async (c) => {
const data = await c.req.json()
await c.req.raw.json() // Body already used!
return c.json(data)
})
export default app
This is because when you do c.req.raw, the body to be used is already consumed. This is explained in https://github.com/honojs/hono/pull/4382#issue-3370569516.
Solution
In this PR, it will create a new Request object with a cached body when c.req.raw is called. The reason why it does not use getter/setter and uses Object.defineProperty is that if it were to use getter/setter, it would fail with the spread syntax {...c.req}.
Caveat
The code size will be increased.
The performance may degrade, but it's small.
The author should do the following, if applicable
Bundle size check
| main (393ded9) | #4425 (e84fcb4) | +/- | |
|---|---|---|---|
| Bundle Size (B) | 18,294B | 18,557B | 263B |
| Bundle Size (KB) | 17.87K | 18.12K | 0.25K |
Compiler Diagnostics (tsc)
| main (393ded9) | #4425 (e84fcb4) | +/- | |
|---|---|---|---|
| Files | 300 | 300 | 0 |
| Lines | 140,207 | 140,227 | 20 |
| Identifiers | 127,056 | 127,083 | 27 |
| Symbols | 264,250 | 264,292 | 42 |
| Types | 155,889 | 155,935 | 46 |
| Instantiations | 538,872 | 538,826 | -46 |
| Memory used | 310,269K | 318,514K | 8,245K |
| I/O read | 0.02s | 0.03s | 0.01s |
| I/O write | 0s | 0s | 0s |
| Parse time | 0.72s | 0.73s | 0.01s |
| Bind time | 0.3s | 0.3s | 0s |
| Check time | 2.67s | 2.77s | 0.1s |
| Emit time | 0s | 0s | 0s |
| Total time | 3.69s | 3.8s | 0.11s |
Compiler Diagnostics (typescript-go)
| main (393ded9) | #4425 (e84fcb4) | +/- | |
|---|---|---|---|
| Files | 269 | 269 | 0 |
| Lines | 118,096 | 118,116 | 20 |
| Identifiers | 116,489 | 116,516 | 27 |
| Symbols | 353,957 | 353,972 | 15 |
| Types | 277,831 | 277,878 | 47 |
| Instantiations | 3,528,587 | 3,528,359 | -228 |
| Memory used | 223,102K | 223,163K | 61K |
| Memory allocs | 10,491,846 | 10,491,879 | 33 |
| Parse time | 0.07s | 0.082s | 0.012s |
| Bind time | 0.024s | 0.023s | -0.001s |
| Check time | 1.426s | 1.616s | 0.19s |
| Emit time | 0s | 0s | 0s |
| Total time | 1.534s | 1.723s | 0.189s |
Reported by octocov
HTTP Performance Benchmark
| Framework | Runtime | Average | Ping | Query | Body |
|---|---|---|---|---|---|
| hono (origin/main) | bun | 36,007.10 | 48,800.58 | 31,322.64 | 27,898.07 |
| hono (current) | bun | 35,418.11 | 47,988.25 | 30,267.06 | 27,999.03 |
| Change | -1.64% | -1.66% | -3.37% | +0.36% |
Bundle size check
| main (393ded9) | #4425 (eacb440) | +/- | |
|---|---|---|---|
| Bundle Size (B) | 18,294B | 18,557B | 263B |
| Bundle Size (KB) | 17.87K | 18.12K | 0.25K |
Compiler Diagnostics (tsc)
| main (393ded9) | #4425 (eacb440) | +/- | |
|---|---|---|---|
| Files | 300 | 300 | 0 |
| Lines | 140,207 | 140,228 | 21 |
| Identifiers | 127,056 | 127,083 | 27 |
| Symbols | 264,250 | 264,292 | 42 |
| Types | 155,889 | 155,935 | 46 |
| Instantiations | 538,872 | 538,826 | -46 |
| Memory used | 310,269K | 308,090K | -2,179K |
| I/O read | 0.02s | 0.03s | 0.01s |
| I/O write | 0s | 0s | 0s |
| Parse time | 0.72s | 0.78s | 0.06s |
| Bind time | 0.3s | 0.3s | 0s |
| Check time | 2.67s | 2.77s | 0.1s |
| Emit time | 0s | 0s | 0s |
| Total time | 3.69s | 3.86s | 0.17s |
Compiler Diagnostics (typescript-go)
| main (393ded9) | #4425 (eacb440) | +/- | |
|---|---|---|---|
| Files | 269 | 269 | 0 |
| Lines | 118,096 | 118,117 | 21 |
| Identifiers | 116,489 | 116,516 | 27 |
| Symbols | 353,957 | 353,972 | 15 |
| Types | 277,831 | 277,878 | 47 |
| Instantiations | 3,528,587 | 3,528,359 | -228 |
| Memory used | 223,102K | 223,175K | 73K |
| Memory allocs | 10,491,846 | 10,491,870 | 24 |
| Parse time | 0.07s | 0.078s | 0.008s |
| Bind time | 0.024s | 0.016s | -0.008s |
| Check time | 1.426s | 1.509s | 0.083s |
| Emit time | 0s | 0s | 0s |
| Total time | 1.534s | 1.605s | 0.071s |
Reported by octocov
HTTP Performance Benchmark
| Framework | Runtime | Average | Ping | Query | Body |
|---|---|---|---|---|---|
| hono (origin/main) | bun | 38,250.69 | 53,142.55 | 33,106.71 | 28,502.80 |
| hono (current) | bun | 37,401.78 | 52,662.68 | 31,646.26 | 27,896.40 |
| Change | -2.22% | -0.90% | -4.41% | -2.13% |
Codecov Report
:x: Patch coverage is 94.28571% with 2 lines in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 91.28%. Comparing base (393ded9) to head (4c411e6).
| Files with missing lines | Patch % | Lines |
|---|---|---|
| src/request.ts | 94.28% | 2 Missing :warning: |
Additional details and impacted files
@@ Coverage Diff @@
## main #4425 +/- ##
==========================================
+ Coverage 91.25% 91.28% +0.02%
==========================================
Files 171 171
Lines 10913 10933 +20
Branches 3144 3149 +5
==========================================
+ Hits 9959 9980 +21
+ Misses 953 952 -1
Partials 1 1
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.