ETag
An ETag lets a client ask “has this changed?” and get an empty answer when it has not. The saving is bandwidth, not work — your resource still runs to produce the response the ETag is calculated from.
Summary
The ETag middleware adds ETag and Last-Modified headers to responses, and
answers 304 Not Modified when a client’s If-None-Match matches. It is added
to resources using a resource group.
The highlighted section below shows how it is added:
Deno
// Code is shortened for brevity
import { ResourceGroup } from "@drashland/drash/modules/http.native.js";
import { ETag } from "@drashland/drash/modules/middleware/ETag.js";
const group = ResourceGroup
.builder()
.middleware(ETag()) // Calling it with no options uses the defaults
.resources(Users)
.build();Like the other pre-built middleware, ETag is a factory. Calling it returns a
middleware class with your options already bound to it.
Configuring Options
The Defaults
| Option | Type | Default | Notes |
|---|---|---|---|
etag_max_length | number | 27 | Characters of the body to encode — currently ignored |
weak | boolean | false | Emit a weak validator (W/"...") |
Options you leave out keep their defaults, so a partial object is safe.
Emitting Weak Validators
A weak ETag (W/"abc") tells caches two representations are semantically
equivalent even when the bytes differ. Set it when byte-for-byte equality is not
what you want compared:
// Responses that differ only in, say, a timestamp will still be treated as
// unchanged by caches.
ETag({
weak: true,
});About etag_max_length
This option currently has no effect. The middleware declares it as
etag_max_length, but the code that builds the value
(ETagResponse.etagHeader()) reads max_hash_length — a different
property. What you pass is therefore undefined where it is used, and the
internal default of 27 applies regardless. Both defaults are 27, which is
why the mismatch is easy to miss.
// Accepted, but ignored. The value is still cut at 27.
ETag({
etag_max_length: 64,
});The number is not a hash length either. The value is built by truncating the response body to that many characters, base64-encoding the result, and prefixing it with the body’s full length in hex:
"a-T2ggc28gZWFzeQ=="
│ └── base64 of the first 27 characters of the body
└───── full body length, in hex (10 characters)How It Works
TLDR
Your resource runs and produces a response. The middleware calculates an ETag
from that response’s body. If the client already has that exact ETag, the body
is thrown away and a 304 is sent instead.
Detailed Explanation
The middleware runs after your resource, because it needs the response body to calculate anything. What happens next depends on the request:
| Request | Result |
|---|---|
No If-None-Match | Response gains ETag and Last-Modified |
If-None-Match matches the current ETag | 304 Not Modified, empty body |
If-None-Match does not match | Full response with a fresh ETag |
An empty response body still gets an ETag. The encoding of empty content is stable, so repeat requests for an empty resource still revalidate correctly.
Data Flow
As you can see above, your resource is called in both flows. The 304 saves
sending the body, not producing it.
Extending This Middleware
The module exports the class as well as the factory, so you can extend it:
import { ETagMiddleware } from "@drashland/drash/modules/middleware/ETag.js";
class LoggedETag extends ETagMiddleware {
constructor() {
// Your options go here, the same ones you would pass to ETag().
super({ weak: true });
}
public override ALL(request: Request) {
console.log(`If-None-Match: ${request.headers.get("if-none-match")}`);
// Hand it back to the original middleware and get out of the way.
return super.ALL(request);
}
}| Export | What It Is |
|---|---|
ETag | The factory. Returns a configured middleware class |
ETagMiddleware | The middleware class itself, for extending |
defaultOptions | The defaults listed above |
Options | The options type |
Related Files
ETag.ts is the middleware. The one class it is built from lives in
modules/middleware/e_tag/. You reach for it only when building an ETag response the
middleware would otherwise build for you.
ETagResponse
import { ETagResponseBuilder } from "@drashland/drash/modules/middleware/e_tag/ETagResponse.js";ETagResponseBuilder extends
ResponseBuilder, taking an existing
Response in its constructor and adding the ETag header to it. The module also exports a
response(response) factory of the same shape as the other builders.
| Method | Returns | Notes |
|---|---|---|
addETagHeader(options) | Promise<this> | Computes the ETag and sets the header. |
etagHeader(options) | Promise<string> | "<hexLength>-<hash>", prefixed W/ when options.weak. |
hash(maxLength) | Promise<string> | btoa() of the body, truncated. Defaults to 27 characters. |
It does not override build() — the inherited one applies.