---
title: "ETag and Cache"
description: "Hono provides robust ETag and Cache middleware features that optimize HTTP request-response lifecycles through automated cryptographic hashing, conditional validation, and Web Standard Cache API in..."
last_updated: "2026-10-12T08:14:12.769531+00:00"
canonical_url: "https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/traffic-middleware/etag-and-cache"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/middleware/etag/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts)
- [src/middleware/secure-headers/secure-headers.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/secure-headers/secure-headers.ts)
- [src/middleware/cache/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts)
- [src/middleware/etag/digest.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/digest.ts)
- [src/context.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/context.ts)
- [src/helper/streaming/sse.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/streaming/sse.ts)
- [src/adapter/aws-lambda/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/handler.ts)
- [src/middleware/serve-static/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/serve-static/index.ts)
- [src/utils/crypto.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/crypto.ts)
- [src/middleware/bearer-auth/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/bearer-auth/index.ts)
- [src/middleware/compress/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/compress/index.ts)
- [src/request.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/request.ts)
- [src/middleware/basic-auth/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/basic-auth/index.ts)
- [src/middleware/timing/timing.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/timing/timing.ts)
- [src/jsx/dom/intrinsic-element/components.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/intrinsic-element/components.ts)
- [src/middleware/method-override/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/method-override/index.ts)
- [src/helper/ssg/ssg.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts)
- [src/helper/proxy/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/proxy/index.ts)
- [src/middleware/jwk/keys.test.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jwk/keys.test.json)
- [src/adapter/lambda-edge/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/lambda-edge/handler.ts)
- [src/middleware/language/language.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/language/language.ts)
- [src/helper/ssg/middleware.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts)
- [src/middleware/cors/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cors/index.ts)
- [src/adapter/cloudflare-workers/serve-static.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-workers/serve-static.ts)
- [src/utils/compress.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/compress.ts)
- [src/middleware/request-id/request-id.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/request-id/request-id.ts)
- [src/utils/headers.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/headers.ts)
- [src/utils/buffer.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/buffer.ts)
- [src/middleware/powered-by/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/powered-by/index.ts)
</details>

## Overview

Hono provides robust ETag and Cache middleware features that optimize HTTP request-response lifecycles through automated cryptographic hashing, conditional validation, and Web Standard Cache API integration. By computing SHA-1 or custom digests from response streams, the ETag middleware evaluates `If-None-Match` headers against strong or weak tags to short-circuit redundant transmissions with `304 Not Modified` responses while retaining specified headers. Concurrently, the cache middleware interfaces with runtime storage via `caches.open` and `cache.match`, enforcing status code constraints, vary directive checks, and `Cache-Control` directives to manage cache population and conditional execution.

Sources: [src/middleware/etag/index.ts:21-36](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L21-L36), [src/middleware/etag/index.ts:79-123](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L79-L123), [src/middleware/cache/index.ts:13-35](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L13-L35), [src/middleware/cache/index.ts:64-180](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L64-L180)

## ETag generation and hashing algorithms

### Public API and Generator Initialization

The ETag middleware accepts an `ETagOptions` configuration object defining parameters that control tag formatting, retained headers on validation bypass, and custom hashing logic. When no custom generator is provided via `options.generateDigest`, the initializer falls back to the native `crypto.subtle` API using the `SHA-1` algorithm.

| Option | Type | Default | Purpose |
| :--- | :--- | :--- | :--- |
| `retainedHeaders` | `string[]` | `RETAINED_304_HEADERS` | Headers preserved on `304 Not Modified` responses. |
| `weak` | `boolean` | `false` | Prepends `W/` to generated ETags for weak validation. |
| `generateDigest` | `(body: Uint8Array) => ArrayBuffer \| Promise<ArrayBuffer>` | `crypto.subtle` (SHA-1) | Custom digest generation function for response bodies. |

Sources: [src/middleware/etag/index.ts:9-13](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L9-L13), [src/middleware/etag/index.ts:38-54](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L38-L54), [src/middleware/etag/index.ts:79-82](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L79-L82)

### Digest Calculation and Hex Encoding

The `generateDigest` function reads response bodies iteratively via a `ReadableStream` reader, merging incoming chunks and invoking the digest generator. Once the stream is fully consumed, the resulting `ArrayBuffer` digest is converted into a lowercase hexadecimal string by mapping each byte to a zero-padded two-character hex value.

```typescript
export const generateDigest = async (
  stream: ReadableStream<Uint8Array<ArrayBuffer>> | null,
  generator: (body: Uint8Array<ArrayBuffer>) => ArrayBuffer | Promise<ArrayBuffer>
): Promise<string | null> => {
  if (!stream) {
    return null
  }

  let result: ArrayBuffer | undefined = undefined

  const reader = stream.getReader()
  for (;;) {
    const { value, done } = await reader.read()
    if (done) {
      break
    }

    result = await generator(mergeBuffers(result, value))
  }

  if (!result) {
    return null
  }

  return Array.prototype.map
    .call(new Uint8Array(result), (x) => x.toString(16).padStart(2, '0'))
    .join('')
}
```
Sources: [src/middleware/etag/digest.ts:16-43](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/digest.ts#L16-L43)

> [!NOTE]
> If a response carries an existing `ETag` header from upstream handlers, the ETag middleware bypasses automatic digest calculation entirely and uses the pre-existing tag value.

Sources: [src/middleware/etag/index.ts:90-95](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L90-L95)

## Conditional validation and weak tags

### Execution Walkthrough and Sequence Analysis

Conditional validation in the ETag middleware evaluates incoming `If-None-Match` request headers against computed or pre-existing entity tags. When a request includes an `If-None-Match` header, the middleware inspects the value after downstream handlers execute and the response ETag is resolved. The validation check executes through a strict function call chain: `etag` handler invokes `etagMatches`, which in turn applies `stripWeak` to parse and normalize tags.

```mermaid
sequenceDiagram
    participant Middleware as etag (index.ts)
    participant Matcher as etagMatches (index.ts)
    participant Stripper as stripWeak (index.ts)

    Middleware->>Matcher: etagMatches(etag, ifNoneMatch)
    Matcher->>Stripper: stripWeak(t) for each comma-split token
    Stripper-->>Matcher: normalized token string
    Matcher->>Stripper: stripWeak(etag)
    Stripper-->>Matcher: normalized etag string
    Matcher-->>Middleware: boolean match result
```

The execution sequence follows this precise path:
1. `etag` — The main middleware function extracts the `If-None-Match` request header and awaits downstream execution before reading the `ETag` response header or generating a fresh digest.
   Sources: [src/middleware/etag/index.ts:84-105](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L84-L105)
2. `etagMatches` — Evaluates whether `ifNoneMatch` is non-null, splits the header value by commas and optional whitespace (`/,\s*/`), and checks if any token matches the target ETag.
   Sources: [src/middleware/etag/index.ts:32-36](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L32-L36)
3. `stripWeak` — Strips the weak validation prefix `W/` from the beginning of a tag string using a regular expression replacement (`tag.replace(/^W\//, '')`), allowing weak and strong tags to be compared equivalently during validation.
   Sources: [src/middleware/etag/index.ts:30-30](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L30-L30)

### 304 Response Generation and Retained Headers

When `etagMatches` returns true, the middleware short-circuits the response by constructing a new `304 Not Modified` response instance. This response discards the response body while preserving specific metadata headers defined by `retainedHeaders`. By default, the middleware references `RETAINED_304_HEADERS` to determine which response headers survive the validation bypass.

| Header Constant | Value / Identifier | Purpose |
| :--- | :--- | :--- |
| `RETAINED_304_HEADERS` | `'cache-control'` | Retains caching directives on 304 responses. |
| `RETAINED_304_HEADERS` | `'content-location'` | Retains resource location metadata. |
| `RETAINED_304_HEADERS` | `'date'` | Retains server response timestamp. |
| `RETAINED_304_HEADERS` | `'etag'` | Retains the entity tag for validation tracking. |
| `RETAINED_304_HEADERS` | `'expires'` | Retains expiration time limits. |
| `RETAINED_304_HEADERS` | `'vary'` | Retains cache negotiation criteria. |

Sources: [src/middleware/etag/index.ts:21-28](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L21-L28), [src/middleware/etag/index.ts:107-119](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L107-L119)

> [!NOTE]
> When iterating over response headers during a 304 short-circuit, header keys are converted to lowercase via `key.toLowerCase()` before checking against the `retainedHeaders` index array, ensuring case-insensitive header preservation.

Sources: [src/middleware/etag/index.ts:115-119](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/index.ts#L115-L119)

## Cache middleware and storage adapter

### Cache Storage Initialization and Availability Check

The cache middleware integrates with the Web Standard `Cache API` via `globalThis.caches`. Upon initialization, the `cache` function verifies if `globalThis.caches` is defined in the current runtime. If the storage adapter is unavailable, the middleware checks the `onCacheNotAvailable` option: when set to `false`, it silently suppresses logging; when provided as a custom function, it invokes that handler; otherwise, it logs a default message to the console and returns a pass-through middleware handler that immediately invokes `next()`.

```typescript
    if (!globalThis.caches) {
      if (options.onCacheNotAvailable === false) {
        // suppress log
      } else if (options.onCacheNotAvailable) {
        options.onCacheNotAvailable()
      } else {
        console.log('Cache Middleware is not enabled because caches is not defined.')
      }
      return async (_c, next) => await next()
    }
```
Sources: [src/middleware/cache/index.ts:73-82](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L73-L82)

When `globalThis.caches` is present, the middleware resolves the cache store name. The `cacheName` option accepts either a static string or a dynamic function `(c: Context) => Promise<string> | string`. The resolved store name is passed to `caches.open(cacheName)` to obtain the cache instance.

```typescript
    const cacheName =
      typeof options.cacheName === 'function' ? await options.cacheName(c) : options.cacheName
    const cache = await caches.open(cacheName)
```
Sources: [src/middleware/cache/index.ts:155-157](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L155-L157)

### Request Matching and Key Generation Lifecycle

Before executing downstream handlers, the middleware intercepts incoming requests. Requests are filtered by method and authorization headers: non-`GET` requests or requests containing an `Authorization` header bypass the cache middleware entirely and proceed directly to `next()`.

```typescript
    if (c.req.method !== 'GET' || c.req.raw.headers.has('Authorization')) {
      await next()
      return
    }
```
Sources: [src/middleware/cache/index.ts:139-142](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L139-L142)

For valid `GET` requests, the cache key is constructed starting from the request URL (`c.req.url`). If a custom `keyGenerator` option is supplied, it executes to replace the base key string. When `vary` directives are configured, the middleware appends each specified header name and its URI-encoded request header value to the cache key using a double-colon delimiter format: `::directive=encodedValue`.

```typescript
    let key = c.req.url
    if (options.keyGenerator) {
      key = await options.keyGenerator(c)
    }
    if (varyDirectives) {
      for (const directive of varyDirectives) {
        const value = c.req.raw.headers.get(directive) ?? ''
        key += `::${directive}=${encodeURIComponent(value)}`
      }
    }
```
Sources: [src/middleware/cache/index.ts:144-153](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L144-L153)

Once the cache key is assembled, the middleware queries the storage adapter via `cache.match(key)`. If a matching response is located, a new `Response` is instantiated from the cached response body and headers, short-circuiting further request execution and returning immediately.

```typescript
    const response = await cache.match(key)
    if (response) {
      return new Response(response.body, response)
    }
```
Sources: [src/middleware/cache/index.ts:158-161](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L158-L161)

> [!WARNING]
> Requests bearing an `Authorization` header automatically bypass the cache lookup and storage lifecycle, preventing authenticated user data from being improperly stored in shared cache tiers.

Sources: [src/middleware/cache/index.ts:139-142](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L139-L142)

### Writing to the Cache Store

When a cache miss occurs, the middleware awaits downstream execution (`await next()`). It then verifies whether the resulting response status code is present in the allowed `cacheableStatusCodes` set (which defaults to `[200]`). If the status code is ineligible, the middleware returns without caching.

```typescript
    await next()
    if (!cacheableStatusCodes.has(c.res.status)) {
      return
    }
```
Sources: [src/middleware/cache/index.ts:163-166](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L163-L166)

Eligible responses undergo header augmentation and skip-cache evaluations. If checks pass, the response is cloned via `c.res.clone()`. The write operation to the cache store is governed by the `wait` boolean option:
- When `wait` is set to `true`, Hono awaits the resolution of `cache.put(key, res)` before finishing the middleware handler execution (a requirement for runtimes like Deno).
- When `wait` is `false` (the default), the promise returned by `cache.put(key, res)` is passed to `c.executionCtx.waitUntil()` to allow background storage without blocking the response delivery.

```typescript
    const res = c.res.clone()
    if (options.wait) {
      await cache.put(key, res)
    } else {
      c.executionCtx.waitUntil(cache.put(key, res))
    }
```
Sources: [src/middleware/cache/index.ts:174-179](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L174-L179)

> [!NOTE]
> The `wait` option defaults to `false`. Explicitly setting `wait: true` is mandatory in Deno and other runtime environments that require pending promises to resolve within the request scope before termination.

Sources: [src/middleware/cache/index.ts:44-44](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L44-L44), [src/middleware/cache/index.ts:84-86](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L84-L86)

## Cache control and skipping heuristics

### Heuristics and Skip Inspection

The cache middleware evaluates several inspection rules before deciding whether an eligible response should be stored in the cache. These heuristics examine the `Cache-Control` header, `Set-Cookie` headers, and `Vary` directives to prevent writing private or sensitive data.

```typescript
const shouldSkipCacheControl = (cacheControl: string | null): boolean =>
  !!cacheControl && /(?:^|,\s*)(?:private|no-(?:store|cache))(?:\s*(?:=|,|$))/i.test(cacheControl)
```
Sources: [src/middleware/cache/index.ts:15-16](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L15-L16)

The `shouldSkipCache` function consolidates these checks, returning `true` if any skipping criterion is satisfied:

```typescript
const shouldSkipCache = (
  res: Response,
  optionsVaryDirectives: Set<string> | undefined,
  responseVary: string[]
): boolean =>
  (responseVary.length &&
    (!optionsVaryDirectives || responseVary.some((name) => !optionsVaryDirectives.has(name)))) ||
  shouldSkipCacheControl(res.headers.get('Cache-Control')) ||
  res.headers.has('Set-Cookie')
```
Sources: [src/middleware/cache/index.ts:27-35](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L27-L35)

> [!WARNING]
> Responses containing a `Set-Cookie` header or a `Cache-Control` directive matching `private`, `no-store`, or `no-cache` are automatically excluded from cache writes to prevent leaking user sessions or private records.
> Sources: [src/middleware/cache/index.ts:32-35](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L32-L35)

### Skip Inspection Call Chain

When determining whether to skip caching a response after downstream execution, Hono invokes a dedicated evaluation sequence.

```mermaid
sequenceDiagram
    participant Index as src/middleware/cache/index.ts
    Index->>Index: cache(c, next)
    Index->>Index: shouldSkipCache(res, optionsVaryDirectives, responseVary)
    Index->>Index: shouldSkipCacheControl(cacheControl)
```
Sources: [src/middleware/cache/index.ts:15-17](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L15-L17), [src/middleware/cache/index.ts:27-35](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L27-L35), [src/middleware/cache/index.ts:170-172](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L170-L172)

1. `cache` invokes `shouldSkipCache` passing the response and vary directives.
2. `shouldSkipCache` checks vary conditions and calls `shouldSkipCacheControl`.
3. `shouldSkipCacheControl` tests the `Cache-Control` header string against the regex pattern for `private`, `no-store`, or `no-cache`.

Sources: [src/middleware/cache/index.ts:15-17](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L15-L17), [src/middleware/cache/index.ts:27-35](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L27-L35), [src/middleware/cache/index.ts:170-172](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L170-L172)

### Header Augmentation Options

The middleware can inject configured `Cache-Control` and `Vary` headers into outgoing responses prior to storage evaluation via the internal `addHeader` function.

```typescript
  const addHeader = (c: Context, responseVary: string[]) => {
    if (cacheControlDirectives) {
      const existingDirectives =
        c.res.headers
          .get('Cache-Control')
          ?.split(',')
          .map((d) => d.trim().split('=', 1)[0]) ?? []
      for (const directive of cacheControlDirectives) {
        let [name, value] = directive.trim().split('=', 2)
        name = name.toLowerCase()
        if (!existingDirectives.includes(name)) {
          c.header('Cache-Control', `${name}${value ? `=${value}` : ''}`, { append: true })
        }
      }
    }

    if (varyDirectives) {
      if (responseVary.length === 0) {
        c.header('Vary', Array.from(varyDirectives).join(', '))
      } else {
        const merged = new Set(varyDirectives)
        for (const directive of responseVary) {
          merged.add(directive)
        }
        if (merged.has('*')) {
          c.header('Vary', '*')
        } else {
          c.header('Vary', Array.from(merged).join(', '))
        }
      }
    }
  }
```
Sources: [src/middleware/cache/index.ts:105-136](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L105-L136)

> [!NOTE]
> If `varyDirectives` includes the wildcard token `*`, the middleware throws an error upon initialization because RFC 7231 Section 7.1.4 disallows effective caching when `*` is specified in the `Vary` header.
> Sources: [src/middleware/cache/index.ts:93-99](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts#L93-L99)

## Buffer management for digest hashing

### Chunk Concatenation and Memory Merging

The `generateDigest` utility in `src/middleware/etag/digest.ts` processes streamed response payloads by reading chunks iteratively from a `ReadableStream<Uint8Array<ArrayBuffer>>`. When building the complete hash input across multiple chunks, the internal `mergeBuffers` function combines an existing `ArrayBuffer` with an incoming `Uint8Array<ArrayBuffer>` chunk. If `buffer1` is undefined, it returns `buffer2` directly; otherwise, it allocates a new `ArrayBuffer` matching the combined byte lengths, writes the initial buffer at offset `0`, and copies the incoming chunk at offset `buffer1.byteLength`.

```typescript
const mergeBuffers = (
  buffer1: ArrayBuffer | undefined,
  buffer2: Uint8Array<ArrayBuffer>
): Uint8Array<ArrayBuffer> => {
  if (!buffer1) {
    return buffer2
  }
  const merged = new Uint8Array<ArrayBuffer>(
    new ArrayBuffer(buffer1.byteLength + buffer2.byteLength)
  )
  merged.set(new Uint8Array(buffer1), 0)
  merged.set(buffer2, buffer1.byteLength)
  return merged
}
```
Sources: [src/middleware/etag/digest.ts:1-14](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/digest.ts#L1-L14)

> [!NOTE]
> Successive chunks from the stream reader are accumulated iteratively via `mergeBuffers`, feeding the result into the custom or default `generator` function on every read cycle until the stream signals completion.
> Sources: [src/middleware/etag/digest.ts:26-34](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/digest.ts#L26-L34)

### Stream Reader Loop and Hex Encoding

Once all stream chunks are processed and hashed, the resulting `ArrayBuffer` is converted into a lowercase hexadecimal string by mapping over its bytes with `toString(16).padStart(2, '0')` and joining them. If the input stream is null or yields no result, the generator returns `null`.

```typescript
export const generateDigest = async (
  stream: ReadableStream<Uint8Array<ArrayBuffer>> | null,
  generator: (body: Uint8Array<ArrayBuffer>) => ArrayBuffer | Promise<ArrayBuffer>
): Promise<string | null> => {
  if (!stream) {
    return null
  }

  let result: ArrayBuffer | undefined = undefined

  const reader = stream.getReader()
  for (;;) {
    const { value, done } = await reader.read()
    if (done) {
      break
    }

    result = await generator(mergeBuffers(result, value))
  }

  if (!result) {
    return null
  }

  return Array.prototype.map
    .call(new Uint8Array(result), (x) => x.toString(16).padStart(2, '0'))
    .join('')
}
```
Sources: [src/middleware/etag/digest.ts:16-43](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/etag/digest.ts#L16-L43)

## Related

- [Response Compression](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/traffic-middleware/response-compression)
- [Request Lifecycle](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/core-engine/request-lifecycle)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/llms.txt) for all pages in this wiki.
