Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(middleware): introduce Request ID middleware #3082

Merged
merged 8 commits into from
Jul 13, 2024

Conversation

ryuapp
Copy link
Contributor

@ryuapp ryuapp commented Jul 2, 2024

Request ID middleware generates a unique id for a request.
The unique request id can be used to trace a request end-to-end.

const app = new Hono()

app.use(requestId())
app.get('/', (c) => {
    console.log(c.get('requestId'))
    return c.text('Hello World!')
})

It is also easily customizable with several options.

type RequesIdOptions = {
  // The maximum length of request id.
  // The default value is 255. 
  limitLength?: number
  // The header name used in request id.
  // The default value is 'X-Request-Id'.
  headerName?: string
  // The request id generation function.
  // The default value is 'crypto.randomUUID()'.
  generator?: (c: Context) => string
}

The author should do the following, if applicable

  • Add tests
  • Run tests
  • bun run format:fix && bun run lint:fix to format the code
  • Add TSDoc/JSDoc to document the code

Copy link

codecov bot commented Jul 2, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.16%. Comparing base (7ee829b) to head (35d23a3).
Report is 61 commits behind head on next.

Additional details and impacted files
@@            Coverage Diff             @@
##             next    #3082      +/-   ##
==========================================
+ Coverage   94.75%   96.16%   +1.41%     
==========================================
  Files         136      147      +11     
  Lines       13339    14824    +1485     
  Branches     2237     2599     +362     
==========================================
+ Hits        12639    14256    +1617     
+ Misses        700      568     -132     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ryuapp ryuapp marked this pull request as ready for review July 2, 2024 17:05
@yusukebe
Copy link
Member

yusukebe commented Jul 6, 2024

Hi @ryuapp

I love this middleware since it's simple! I think this has no problem and is ready to merge.

@usualoma @nakasyou @MathurAditya724 @fzn0x and others. Do you have any thoughts on this?

@yusukebe yusukebe added the v4.5 label Jul 6, 2024
@ryuapp
Copy link
Contributor Author

ryuapp commented Jul 7, 2024

I can't decide between requestId and requestID in the variable and middleware name.
The former follows the naming convention and looks better, but I thought 'L' and 'I' were confusing, so I chose the latter.

However, I realized that the above problem is a font problem and does not affect anything other than the document, so I am thinking of fixing it, but does anyone have any opinions?

@fzn0x
Copy link
Contributor

fzn0x commented Jul 7, 2024

I think requestId is better to use when typing on keyboard, and it already use since then and accepted by the majority, so maybe just go with requestId?

@MathurAditya724
Copy link
Contributor

Interesting middleware! Can already think about multiple use cases for this. And I am voting for requestId.

@yusukebe
Copy link
Member

yusukebe commented Jul 7, 2024

I prefer requestId for the same reason as @fzn0x 👍

@ryuapp
Copy link
Contributor Author

ryuapp commented Jul 7, 2024

Thank you for all the opinions.
Many people agree with requestId and the reason is obvious, I will change it to requestId.

Copy link
Contributor

@fzn0x fzn0x left a comment

Choose a reason for hiding this comment

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

Looks good to me

@yusukebe yusukebe changed the base branch from main to next July 8, 2024 14:30
package.json Outdated Show resolved Hide resolved
@ryuapp
Copy link
Contributor Author

ryuapp commented Jul 8, 2024

The tests is failing on next regardless of this branch, I submit a PR to fix it. #3115

Copy link
Member

@usualoma usualoma left a comment

Choose a reason for hiding this comment

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

Thanks for the great pull request!

Performance improvement proposals

If the request ID can be obtained from the request header, it can be assumed that it can be used as is in most cases. String.prototype.replace() is heavy, so it is better not to do anything if it passes the check by the regular expression prepared in advance.

The following is an example implementation.

diff --git a/src/middleware/request-id/request-id.ts b/src/middleware/request-id/request-id.ts
index 0c11cb46..ea66b48b 100644
--- a/src/middleware/request-id/request-id.ts
+++ b/src/middleware/request-id/request-id.ts
@@ -44,13 +44,19 @@ export const requestId = ({
   headerName = 'X-Request-Id',
   generator = () => crypto.randomUUID(),
 }: RequesIdOptions = {}): MiddlewareHandler => {
+  const isValidIncomingHeaderValueRe = new RegExp(
+    `^[\\w\\-]${limitLength ? `{,${limitLength}}` : '+'}$`
+  )
+
   return async function requestId(c, next) {
     // If `headerName` is empty string, req.header will return the object
     let reqId = headerName ? c.req.header(headerName) : undefined
 
     if (reqId) {
-      reqId = reqId.replace(/[^\w\-]/g, '')
-      reqId = limitLength > 0 ? reqId.substring(0, limitLength) : reqId
+      if (!isValidIncomingHeaderValueRe.test(reqId)) {
+        reqId = reqId.replace(/[^\w\-]/g, '')
+        reqId = limitLength > 0 ? reqId.substring(0, limitLength) : reqId
+      }
     } else {
       reqId = generator(c)
     }

src/middleware/request-id/request-id.ts Outdated Show resolved Hide resolved
Co-Authored-By: Taku Amano <taku@taaas.jp>
@ryuapp
Copy link
Contributor Author

ryuapp commented Jul 9, 2024

Hello @usualoma .
Thank you for your proposal.
When I ran a benchmark with Deno, it seems that the cost of checking with regular expressions is greater.

benchmark               time (avg)        iter/s             (min … max)       p75       p99      p995
------------------------------------------------------------------------ -----------------------------
no-check: valid         68.51 ns/iter  14,595,900.4  (60.83 ns … 240.78 ns) 66.26 ns 136.44 ns 158.73 ns
pre check: valid        96.83 ns/iter  10,327,663.8     (85.27 ns … 154 ns) 92.97 ns 148.24 ns 149.35 ns
no-check: invalid       93.97 ns/iter  10,641,957.4  (90.13 ns … 212.15 ns) 92.73 ns 122.85 ns 146.21 ns
pre check: invalid     246.53 ns/iter   4,056,358.6 (235.76 ns … 670.25 ns) 240.52 ns 547.67 ns 621.05 ns

Source codes

const validId = crypto.randomUUID();
const invalidId = validId + "!";
const limitLength = 255;
const isValidIncomingHeaderValueRe = new RegExp(
  `^[\\w\\-]${limitLength ? `{0,${limitLength}}` : "+"}$`,
);

let id;
console.log(isValidIncomingHeaderValueRe);
console.log(!isValidIncomingHeaderValueRe.test(validId));
console.log(!isValidIncomingHeaderValueRe.test(invalidId));
Deno.bench({
  name: "no-check: valid",
  fn() {
    id = validId.replace(/[^\w\-]/g, "");
    id = limitLength > 0 ? id.substring(0, limitLength) : id;
  },
});

Deno.bench({
  name: "pre check: valid",
  fn() {
    if (!isValidIncomingHeaderValueRe.test(validId)) {
      id = validId.replace(/[^\w\-]/g, "");
      id = limitLength > 0 ? id.substring(0, limitLength) : id;
    }
  },
});
Deno.bench({
  name: "no-check: invalid",
  fn() {
    id = invalidId.replace(/[^\w\-]/g, "");
    id = limitLength > 0 ? id.substring(0, limitLength) : id;
  },
});

Deno.bench({
  name: "pre check: invalid",
  fn() {
    if (!isValidIncomingHeaderValueRe.test(invalidId)) {
      id = invalidId.replace(/[^\w\-]/g, "");
      id = limitLength > 0 ? id.substring(0, limitLength) : id;
    }
  },
});

However, as you say, String.prototype.replace() is heavy. The longer the requestid, the higher the load.
This validation is added to respect Ruby on Rails, but it may not be suitable for JavaScript. I also checked other frameworks (primarily written in GO) and they didn't have this kind of functionality.

I'm thinking of removing this validation, leaving only lengthLimit. What do you think about this?

@usualoma
Copy link
Member

usualoma commented Jul 9, 2024

@ryuapp
Oooh, sorry, you are right, my version was slow. First of all, I apologise for taking up your time.

I also benchmarked and confirmed the following.

Checking with a regular expression like const isValidCharsOnlyRe = /^[\w\-]+$/;, "node" and "deno" might perform a bit better on normal systems. In "bun", my approach did not perform well in any way.

scripts

Both benchmarks are the same.

deno
const validId = crypto.randomUUID();
const invalidId = validId + "!";
const limitLength = 255;
const isValidIncomingHeaderValueRe = new RegExp(
  `^[\\w\\-]${limitLength ? `{0,${limitLength}}` : "+"}$`,
);
const isValidCharsOnlyRe = /^[\w\-]+$/;


let id;
console.log(isValidIncomingHeaderValueRe);
console.log(!isValidIncomingHeaderValueRe.test(validId));
console.log(!isValidIncomingHeaderValueRe.test(invalidId));
console.log(!isValidCharsOnlyRe.test(validId));
console.log(!isValidCharsOnlyRe.test(invalidId));
Deno.bench({
  name: "no-check: valid",
  fn() {
    id = validId.replace(/[^\w\-]/g, "");
    id = limitLength > 0 ? id.substring(0, limitLength) : id;
  },
});

Deno.bench({
  name: "pre check: valid",
  fn() {
    if (!isValidIncomingHeaderValueRe.test(validId)) {
      id = validId.replace(/[^\w\-]/g, "");
      id = limitLength > 0 ? id.substring(0, limitLength) : id;
    }
  },
});

Deno.bench({
  name: "pre check w/ isValidCharsOnlyRe: valid",
  fn() {
    if (!isValidCharsOnlyRe.test(validId) || (limitLength > 0 && validId.length > limitLength)) {
      id = validId.replace(/[^\w\-]/g, "");
      id = limitLength > 0 ? id.substring(0, limitLength) : id;
    }
  },
});

Deno.bench({
  name: "no-check: invalid",
  fn() {
    id = invalidId.replace(/[^\w\-]/g, "");
    id = limitLength > 0 ? id.substring(0, limitLength) : id;
  },
});

Deno.bench({
  name: "pre check: invalid",
  fn() {
    if (!isValidIncomingHeaderValueRe.test(invalidId)) {
      id = invalidId.replace(/[^\w\-]/g, "");
      id = limitLength > 0 ? id.substring(0, limitLength) : id;
    }
  },
});
node / bun
import { run, group, bench } from 'mitata'

const validId = crypto.randomUUID()
const invalidId = validId + '!'
const limitLength = 255
const isValidIncomingHeaderValueRe = new RegExp(
  `^[\\w\\-]${limitLength ? `{0,${limitLength}}` : '+'}$`
)
const isValidCharsOnlyRe = /^[\w\-]+$/

let id
console.log(isValidIncomingHeaderValueRe)
console.log(!isValidIncomingHeaderValueRe.test(validId))
console.log(!isValidIncomingHeaderValueRe.test(invalidId))

group('uuid validation', () => {
  bench('no-check: valid', () => {
    id = validId.replace(/[^\w\-]/g, '')
    id = limitLength > 0 ? id.substring(0, limitLength) : id
  })
  bench('pre check: valid', () => {
    if (!isValidIncomingHeaderValueRe.test(validId)) {
      id = validId.replace(/[^\w\-]/g, '')
      id = limitLength > 0 ? id.substring(0, limitLength) : id
    }
  })
  bench('pre check w/ isValidCharsOnlyRe: valid', () => {
    if (!isValidCharsOnlyRe.test(validId) || (limitLength > 0 && validId.length > limitLength)) {
      id = validId.replace(/[^\w\-]/g, '')
      id = limitLength > 0 ? id.substring(0, limitLength) : id
    }
  })
  bench('no-check: invalid', () => {
    id = invalidId.replace(/[^\w\-]/g, '')
    id = limitLength > 0 ? id.substring(0, limitLength) : id
  })
  bench('pre check: invalid', () => {
    if (!isValidIncomingHeaderValueRe.test(invalidId)) {
      id = invalidId.replace(/[^\w\-]/g, '')
      id = limitLength > 0 ? id.substring(0, limitLength) : id
    }
  })
})

run()

deno

benchmark                                   time (avg)        iter/s             (min … max)       p75       p99      p995
-------------------------------------------------------------------------------------------- -----------------------------
no-check: valid                             50.84 ns/iter  19,667,727.7       (48.6 ns … 74 ns) 51.19 ns 59.42 ns 66.02 ns
pre check: valid                            61.92 ns/iter  16,150,143.1   (59.85 ns … 74.46 ns) 62.24 ns 65.65 ns 66.93 ns
pre check w/ isValidCharsOnlyRe: valid      35.25 ns/iter  28,369,004.6   (33.86 ns … 40.23 ns) 35.56 ns 37.77 ns 38.45 ns
no-check: invalid                           76.43 ns/iter  13,083,104.8    (72.61 ns … 97.5 ns) 77.72 ns 87.95 ns 90.08 ns
pre check: invalid                         181.03 ns/iter   5,524,007.4 (174.89 ns … 193.31 ns) 183.09 ns 191.79 ns 192.09 ns

node

• uuid validation
------------------------------------------------------------------------------ -----------------------------
no-check: valid                          46.04 ns/iter     (43.56 ns … 131 ns)  45.51 ns   68.5 ns  80.02 ns
pre check: valid                         59.99 ns/iter   (56.76 ns … 81.89 ns)  60.77 ns  67.26 ns  73.71 ns
pre check w/ isValidCharsOnlyRe: valid   33.85 ns/iter   (32.67 ns … 66.59 ns)  34.02 ns  39.79 ns  46.81 ns
no-check: invalid                        67.87 ns/iter     (64.53 ns … 139 ns)  67.55 ns  92.77 ns    115 ns
pre check: invalid                         169 ns/iter       (159 ns … 209 ns)    169 ns    195 ns    207 ns

bun

• uuid validation
------------------------------------------------------------------------------ -----------------------------
no-check: valid                            1.8 ns/iter      (1.63 ns … 219 ns)   1.75 ns   2.28 ns   9.09 ns
pre check: valid                          2.29 ns/iter    (2.18 ns … 21.18 ns)   2.26 ns   2.83 ns   6.19 ns
pre check w/ isValidCharsOnlyRe: valid     2.4 ns/iter    (2.24 ns … 38.45 ns)   2.38 ns   3.01 ns   6.73 ns
no-check: invalid                          1.8 ns/iter    (1.69 ns … 99.83 ns)   1.79 ns   2.22 ns   5.33 ns
pre check: invalid                        2.02 ns/iter    (1.89 ns … 23.52 ns)   2.01 ns   2.44 ns   6.39 ns

I'm thinking about this now.

I'm thinking of removing this validation, leaving only the lengthLimit What do you think about this?

@usualoma
Copy link
Member

usualoma commented Jul 9, 2024

Ah, using const hasInvalidCharRe = /[^\w\-]/ gave the best performance for valid id, whether "node", "deno" or "bun".

script
import { run, group, bench } from 'mitata'

const validId = crypto.randomUUID()
const invalidId = validId + '!'
const limitLength = 255
const isValidIncomingHeaderValueRe = new RegExp(
  `^[\\w\\-]${limitLength ? `{0,${limitLength}}` : '+'}$`
)
const isValidCharsOnlyRe = /^[\w\-]+$/
const hasInvalidCharRe = /[^\w\-]/

let id
console.log(isValidIncomingHeaderValueRe)
console.log(!isValidIncomingHeaderValueRe.test(validId))
console.log(!isValidIncomingHeaderValueRe.test(invalidId))

group('uuid validation', () => {
  bench('no-check: valid', () => {
    id = validId.replace(/[^\w\-]/g, '')
    id = limitLength > 0 ? id.substring(0, limitLength) : id
  })
  bench('pre check: valid', () => {
    if (!isValidIncomingHeaderValueRe.test(validId)) {
      id = validId.replace(/[^\w\-]/g, '')
      id = limitLength > 0 ? id.substring(0, limitLength) : id
    }
  })
  bench('pre check w/ isValidCharsOnlyRe: valid', () => {
    if (!isValidCharsOnlyRe.test(validId) || (limitLength > 0 && validId.length > limitLength)) {
      id = validId.replace(/[^\w\-]/g, '')
      id = limitLength > 0 ? id.substring(0, limitLength) : id
    }
  })
  bench('pre check w/ hasInvalidCharRe: valid', () => {
    if (hasInvalidCharRe.test(validId) || (limitLength > 0 && validId.length > limitLength)) {
      id = validId.replace(/[^\w\-]/g, '')
      id = limitLength > 0 ? id.substring(0, limitLength) : id
    }
  })
  bench('no-check: invalid', () => {
    id = invalidId.replace(/[^\w\-]/g, '')
    id = limitLength > 0 ? id.substring(0, limitLength) : id
  })
  bench('pre check: invalid', () => {
    if (!isValidIncomingHeaderValueRe.test(invalidId)) {
      id = invalidId.replace(/[^\w\-]/g, '')
      id = limitLength > 0 ? id.substring(0, limitLength) : id
    }
  })
})

run()

deno

• uuid validation
------------------------------------------------------------------------------ -----------------------------
no-check: valid                          47.77 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !
pre check: valid                         59.64 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !
pre check w/ isValidCharsOnlyRe: valid    36.8 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !
pre check w/ hasInvalidCharRe: valid     35.77 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !
no-check: invalid                        72.89 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !
pre check: invalid                         174 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !

node

• uuid validation
------------------------------------------------------------------------------ -----------------------------
no-check: valid                          46.79 ns/iter     (42.56 ns … 139 ns)  45.65 ns  67.34 ns  79.71 ns
pre check: valid                         61.04 ns/iter     (56.62 ns … 274 ns)  60.91 ns  81.44 ns    227 ns
pre check w/ isValidCharsOnlyRe: valid   33.54 ns/iter   (31.78 ns … 61.83 ns)  33.26 ns  38.43 ns  43.01 ns
pre check w/ hasInvalidCharRe: valid     32.59 ns/iter   (30.66 ns … 57.82 ns)  33.06 ns  37.76 ns  40.98 ns
no-check: invalid                        67.71 ns/iter     (62.32 ns … 123 ns)  67.59 ns  92.26 ns    108 ns
pre check: invalid                         169 ns/iter       (162 ns … 243 ns)    170 ns    201 ns    241 ns

bun

• uuid validation
------------------------------------------------------------------------------ -----------------------------
no-check: valid                           1.76 ns/iter      (1.59 ns … 179 ns)   1.69 ns   3.34 ns  10.23 ns
pre check: valid                           2.2 ns/iter    (2.05 ns … 18.33 ns)   2.16 ns   2.85 ns    6.9 ns
pre check w/ isValidCharsOnlyRe: valid    2.37 ns/iter    (2.24 ns … 33.63 ns)   2.34 ns   3.07 ns   7.02 ns
pre check w/ hasInvalidCharRe: valid      1.55 ns/iter    (1.44 ns … 25.37 ns)   1.55 ns   1.95 ns   5.72 ns
no-check: invalid                         1.74 ns/iter     (1.63 ns … 22.5 ns)   1.73 ns   2.18 ns   6.37 ns
pre check: invalid                        1.96 ns/iter    (1.83 ns … 26.43 ns)   1.93 ns    2.4 ns   6.73 ns

However...

@usualoma
Copy link
Member

@ryuapp
I think it is better that the character type is validated, as we do not want attacks to occur due to unexpected strings in the "X-Request-Id". (So your first version of the approach is correct, in my opinion.) While "deno", "node" and "bun" seem to only accept latin-1 characters in the header, Cloudflare Workers & Pages can also accept multibyte characters. In the case of multibyte characters, it is undesirable to simply use substring() to cut them out, as this may break the string.

CleanShot 2024-07-10 at 07 44 48@2x

The longer the requestid, the higher the load.

With regard to this, the topic is "resistance to DoS attacks that send headers like a few KB with the intention of attacking", isn't it? I think that is certainly true.

As another suggestion, how about "checking the length and character type of the header value and re-generating it if the result is invalid"?

diff --git a/src/middleware/request-id/request-id.ts b/src/middleware/request-id/request-id.ts
index 0c11cb46..0c2bbd28 100644
--- a/src/middleware/request-id/request-id.ts
+++ b/src/middleware/request-id/request-id.ts
@@ -48,10 +48,7 @@ export const requestId = ({
     // If `headerName` is empty string, req.header will return the object
     let reqId = headerName ? c.req.header(headerName) : undefined
 
-    if (reqId) {
-      reqId = reqId.replace(/[^\w\-]/g, '')
-      reqId = limitLength > 0 ? reqId.substring(0, limitLength) : reqId
-    } else {
+    if (!reqId || reqId.length > limitLength || /[^\w\-]/.test(reqId)) {
       reqId = generator(c)
     }

In general, if the value of the header is invalid, I think it can be regarded as an 'illegal access'. If the header value is invalid with a proper access (although such use cases are almost never the case), it would be better to modify the system or deal with it independently in the application without using this middleware.

If the application wants to accept non-UUID or longer strings, it can do this.

app.use(requestId({
   generator: (c) => c.req.header('X-Amzn-Trace-Id'), // like "Self=1-67891234-12456789abcdef012345678;Root=1-67891233-abcdef012345678912345678"
}))

benchmark

Benchmarking with the following scripts showed the following. I consider this a good result because I think we should focus on two things. 'high performance in the normal case' and 'not so bad in the worst case.'

  • Normal case : performance improved slightly.
  • Short malformed headers: worse performance, but acceptable
  • Long malformed headers: significant performance improvement
script
import { run, group, bench } from 'mitata'

let id
const limitLength = 255

const data = [
  { label: 'valid', id: crypto.randomUUID() },
  { label: 'short invalid', id: crypto.randomUUID() + '!' },
  { label: 'long invalid', id: '!'.repeat(2048) },
]

data.forEach(({ label, id: testId }) => {
  group(label, () => {
    bench('replace() and substring()', () => {
      id = testId.replace(/[^\w\-]/g, '')
      id = limitLength > 0 ? id.substring(0, limitLength) : id
    })
    bench('regenerate', () => {
      if (!testId || testId.length > limitLength || /[^\w\-]/.test(testId)) {
        id = crypto.randomUUID()
      }
    })
  })
})

run()

deno

benchmark                      time (avg)             (min … max)       p75       p99      p999
----------------------------------------------------------------- -----------------------------
• valid
----------------------------------------------------------------- -----------------------------
replace() and substring()   46.74 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !
regenerate                  35.97 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !

summary for valid
  regenerate
   1.3x faster than replace() and substring()

• shot invalid
----------------------------------------------------------------- -----------------------------
replace() and substring()   80.77 ns/iter         (0 ps … 977 ns)      0 ps    977 ns    977 ns !
regenerate                    523 ns/iter         (0 ps … 977 ns)    977 ns    977 ns    977 ns !

summary for shot invalid
  replace() and substring()
   6.48x faster than regenerate

• long invalid
----------------------------------------------------------------- -----------------------------
replace() and substring()  30'126 ns/iter (29'297 ns … 31'250 ns) 30'273 ns 31'250 ns 31'250 ns
regenerate                    481 ns/iter         (0 ps … 977 ns)    977 ns    977 ns    977 ns !

summary for long invalid
  regenerate
   62.63x faster than replace() and substring()

node

• valid
----------------------------------------------------------------- -----------------------------
replace() and substring()    46.3 ns/iter     (42.34 ns … 122 ns)  45.67 ns  70.21 ns  89.27 ns
regenerate                  37.01 ns/iter     (33.67 ns … 106 ns)  36.36 ns  58.19 ns   70.7 ns

summary for valid
  regenerate
   1.25x faster than replace() and substring()

• shot invalid
----------------------------------------------------------------- -----------------------------
replace() and substring()   74.88 ns/iter     (69.13 ns … 121 ns)  74.83 ns    102 ns    119 ns
regenerate                    156 ns/iter       (140 ns … 509 ns)    169 ns    194 ns    249 ns

summary for shot invalid
  replace() and substring()
   2.08x faster than regenerate

• long invalid
----------------------------------------------------------------- -----------------------------
replace() and substring()  31'356 ns/iter (29'791 ns … 61'084 ns) 31'209 ns 35'958 ns 41'333 ns
regenerate                    117 ns/iter       (107 ns … 177 ns)    132 ns    145 ns    169 ns

summary for long invalid
  regenerate
   267.92x faster than replace() and substring()

bun

benchmark                      time (avg)             (min … max)       p75       p99      p999
----------------------------------------------------------------- -----------------------------
• valid
----------------------------------------------------------------- -----------------------------
replace() and substring()   60.92 ns/iter     (56.58 ns … 421 ns)  61.38 ns    109 ns    267 ns
regenerate                   49.2 ns/iter      (47.2 ns … 314 ns)   49.6 ns  57.41 ns    247 ns

summary for valid
  regenerate
   1.24x faster than replace() and substring()

• shot invalid
----------------------------------------------------------------- -----------------------------
replace() and substring()   83.73 ns/iter     (78.29 ns … 322 ns)  84.57 ns  96.84 ns    300 ns
regenerate                  85.18 ns/iter     (79.24 ns … 340 ns)  86.69 ns    101 ns    306 ns

summary for shot invalid
  replace() and substring()
   1.02x faster than regenerate

• long invalid
----------------------------------------------------------------- -----------------------------
replace() and substring()  11'056 ns/iter (10'500 ns … 33'667 ns) 10'959 ns 15'333 ns 23'625 ns
regenerate                  32.88 ns/iter      (28.6 ns … 302 ns)  33.47 ns   42.5 ns    116 ns

summary for long invalid
  regenerate
   336.29x faster than replace() and substring()

What do you think?

@ryuapp
Copy link
Contributor Author

ryuapp commented Jul 10, 2024

@usualoma Thank you for investigating this.
I agree with your opinion.
Of all the ideas we can think of at the moment, I think "re-generating" makes the most sense and is a good idea.
I was concerned that Deno's benchmarks were slow, but it seems like crypto.ramdomUUID() on Deno is just slow.
We can ignore this issue.

ok. I adopt the proposal. Thanks so much!

ryuapp and others added 2 commits July 10, 2024 22:53
@usualoma
Copy link
Member

@ryuapp Thank you!

Copy link
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
Member

@usualoma Thank you for reviewing, @ryuapp Thank you for such great middleware.

Now, I'll merge this into the next!

@yusukebe yusukebe merged commit e6d253d into honojs:next Jul 13, 2024
14 checks passed
@ryuapp ryuapp deleted the add-request-id branch July 13, 2024 05:41
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.

5 participants