Skip to content

ETag changes every time when combine etag and cache middlewares #4071

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

Open
rwv opened this issue Apr 14, 2025 · 2 comments
Open

ETag changes every time when combine etag and cache middlewares #4071

rwv opened this issue Apr 14, 2025 · 2 comments
Labels

Comments

@rwv
Copy link
Contributor

rwv commented Apr 14, 2025

What version of Hono are you using?

4.7.6

What runtime/platform is your app running on? (with version if possible)

Cloudflare Workers

What steps can reproduce the bug?

Repository: https://github.com/rwv/hone-etag-large-payload-test
Live demo: https://hono-etag-large-payload-test.rwv.dev/

Steps:

  1. Load the page
  2. Refresh
  3. Observe that the ETag header changes each time

What is the expected behavior?

For identical response bodies, the ETag should remain stable across requests, regardless of how the body is chunked.

What do you see instead?

When combining the etag() and cache() middleware on Cloudflare Workers, the ETag value changes on every request—even when the response body remains the same. This breaks 304 behavior and prevents effective caching.

Additional information

Suspected Cause

On Cloudflare Workers, responses returned from the cache expose .body as a ReadableStream. The chunk structure of this stream is not consistent across requests.

This breaks the assumption made in #3604:

“It is unlikely that the same architecture (server) will have different data divisions.”

The above is speculative, based on observed behavior.

Suggestion

PR #3832 introduced the generateDigest(body: Uint8Array) option, which is useful but not sufficient in this case.

generateETag?: (res: ClonedResponse or stream?) => string | Promise<string>

This would:

  • Native use of DigestStream in Cloudflare Workers for consistent hashing
  • Better semantic clarity: digesting a stream should take a stream, not a fully merged Uint8Array
  • Avoids confusion: the current (body: Uint8Array) => ArrayBuffer signature suggests that Hono reads the full body first, which it doesn’t. The chunked-digest logic is hidden from users and may cause incorrect assumptions.
@rwv rwv added the triage label Apr 14, 2025
@rwv
Copy link
Contributor Author

rwv commented May 13, 2025

Hi, any update on this? Happy to provide more context or help with a PR if needed.
Thanks in advance!

@usualoma
Copy link
Member

Hi @rwv, thank you for creating the issue.

SubtleCrypto does not support incremental digest generation, so Hono's cache middleware is currently designed as it is.

In general, responses that are divided into multiple chunks are often better generated in advance at an appropriate time before the etag middleware.

In your application's case, generating the ETag before the cache middleware would be advantageous as it would allow the generated ETag value to be cached along with the response.

diff --git i/src/index.ts w/src/index.ts
index 9a97ad7..4b3d9d1 100644
--- i/src/index.ts
+++ w/src/index.ts
@@ -12,6 +12,19 @@ app.use(
     cacheControl: "max-age=3600",
   })
 );
+app.use("*", async (c, next) => {
+  const body = c.res.clone().body;
+  if (body) {
+    const digestStream = new crypto.DigestStream("SHA-256");
+    body.pipeTo(digestStream);
+    const digest = await digestStream.digest;
+    const hexString = [...new Uint8Array(digest)]
+      .map((b) => b.toString(16).padStart(2, "0"))
+      .join("");
+    c.res.headers.set("ETag", `W/"${hexString}"`);
+  }
+  next();
+});
 
 // Generate a payload of the given size
 function generatePayload(size: number) {

Given this situation, we do not anticipate changes to the etag middleware API until SubtleCrypto supports incremental generation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants
@usualoma @rwv and others