If the upstream response doesn't include an `ETag` header, the ETag
middleware needs to clone the response so it can read the body to
compute an ETag. This commit simplifies the management of the original
and cloned responses.
* chore(etag): reduce global state in tests
Previously, the `etag` tests used a single Hono `app` that was
instantiated statically before the tests. Having a shared app increases
the chances of inter-test conflicts and makes it impossible to modify
the app for a single test. This, in turn, makes it harder for test
maintainers to see the connection between the app configuration and the
expectations.
This commit moves the creation of the `app` into a `beforeEach` block
and moves any app configuration that relates to a single test case into
that test case.
* perf(etag): don't override ETags from upstream
Previously, the `etag` middleware would always compute an `ETag`
header based on the SHA-1 of the `Response` body. In cases where the
upstream response already includes the header, this wastefully
clones the response, reads the body into memory, and computes a hash.
This commit changes the middleware to bypass the hash computation if
the `Response` already has an ETag.
* perf(etag): 304s include only necessary headers
The spec[^1] for 304 Not Modified says,
> The server generating a 304 response MUST generate any of the
> following header fields that would have been sent in a 200 (OK)
> response to the same request:
>
> * Content-Location, Date, ETag, and Vary
> * Cache-Control and Expires (see [CACHING])
>
> Since the goal of a 304 response is to minimize information transfer
> when the recipient already has one or more cached representations, a
> sender SHOULD NOT generate representation metadata other than the
> above listed fields unless said metadata exists for the purpose of
> guiding cache updates (e.g., Last-Modified might be useful if the
> response does not have an ETag field).
Previously, the `etag` middleware was sending all headers from the
original response except for `content-type` (omitted by
`Context.prototype.set res`) and `content-length` (omitted by the `etag`
middleware itself).
This commit changes the middleware to include only the headers
required with the spec, upgrading it from _conditionally compliant_ to
_fully compliant_. The list is configurable as
`options.retainedHeaders`.
[^1]: https://www.rfc-editor.org/rfc/rfc9110#name-304-not-modified
* fix(etag): Support multi-value If-None-Match
The spec[^1] for `If-None-Match` says,
> If the field value is a list of entity tags, the condition is false if
> one of the listed tags matches the entity tag of the selected
> representation.
and provides these examples:
```
If-None-Match: "xyzzy"
If-None-Match: W/"xyzzy"
If-None-Match: "xyzzy", "r2d2xxxx", "c3piozzzz"
If-None-Match: W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"
If-None-Match: *
```
The value `*` isn't relevant for this middleware, but the other values
are.
This commit adds support for multi-value comma-separated ETags in the
`If-None-Match` header.
[^1]: https://www.rfc-editor.org/rfc/rfc9110#name-if-none-match
* perf(etag): prefer Header.prototype.get
Previously, the `etag` middleware used `c.req.header(...)` to access the
`If-None-Match` header. This method works, but is less efficient than
accessing the value via the raw `Headers` object.
Additionally, the middleware was trying multiple case versions for the
same header, but header-lookup is case-insensitive, so it was wasting
time on requests that lacked the header altogether.
This change also makes `etag` consistent with the other middleware in
this repo (e.g. `basic-auth`, `compress`), which all use
`c.req.headers.get(...)` to access headers.
* feat: `env` support enviroment variables for multi runtimes
* typo
* denoify
* fixed ci settings
* fixed deno command
* comment out lagon test
* remove warnings