Skip to Content
Drash v3 is in beta. APIs may change.

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:

// 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

OptionTypeDefaultNotes
etag_max_lengthnumber27Characters of the body to encode — currently ignored
weakbooleanfalseEmit 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:

RequestResult
No If-None-MatchResponse gains ETag and Last-Modified
If-None-Match matches the current ETag304 Not Modified, empty body
If-None-Match does not matchFull 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); } }
ExportWhat It Is
ETagThe factory. Returns a configured middleware class
ETagMiddlewareThe middleware class itself, for extending
defaultOptionsThe defaults listed above
OptionsThe options type

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.

MethodReturnsNotes
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.

Last updated on