vapr-conditionals

0.2.1 • Public • Published

vapr-conditionals Build Status

Installation

npm install --save vapr
npm install --save vapr-conditionals

Usage

This plugin enables conditional requests. Specifically, it handles If-Match, If-None-Match, If-Modified-Since, and If-Unmodified-Since headers, while providing clients with ETag and Last-Modified headers.

Conditional requests can make your server more efficient by saving bandwidth on responses that don't change very often. Also, they can empower clients to avoid certain race conditions.

When you add this plugin to a route, a new function called req.validate() becomes available. You must call req.validate() exactly once before returning a successful response. When you do, you can provide it with a lastModified date, a weak ETag, or a strong ETag (see below for details). The req.validate() function can throw a 304 Not Modified or a 412 Precondition Failed response, or it can simply return normally, allowing your app to generate its typical (2xx) response.

const conditionals = require('vapr-conditionals');
const app = require('vapr')();
const route = app.get('/foo');

route.use(conditionals());
route.use((req) => {
	req.validate({ lastModified: new Date(someTimestamp) });
	return [[someData]];
});

Any checks that you perform which may cause a 3xx or 4xx response should be done before calling req.validate(). In other words, req.validate() should only be called immediately before generating a successful response (or immediately before perfoming any meaningful actions, in the case of POST, PUT, DELETE, or PATCH requests).

Options

options.lastModified = null

The simplest way to use this plugin is to call req.validate() with the lastModified option, which must be a Date object. The given date should represent the last time that the requested resource was modified (or created). If the requested resource does not exist, you can use null instead of a Date object (or simply call req.validate() without any options).

req.validate({ lastModified: new Date(someTimestamp) });

When you use this approach, a weak ETag header will automatically be generated from the given date, and the Last-Modified header will also be sent as a fallback for older clients that don't support ETags.

Don't use this approach if any of the following statements are true:

  • You'd like to invalidate caches based on something other than the given lastModified date.
  • Your clients need guaranteed data freshness with a precision finer than 1 second.
  • Your clients need to use the If-Match header to avoid certain race conditions.
  • You don't want to support conditional requests for legacy clients that don't support ETags.

options.weak = null

If you need to invalidate caches based on factors other than a lastModified date, you can instead call req.validate() with the weak option, which must be an array of strings and/or Buffers. All data in the array will be hashed and combined to generate a single weak ETag. If any element of the array is different from one request to another, the generated ETags will also be different (which will invalidate caches). If the requested resource does not exist, you can use null instead of an array (or simply call req.validate() without any options).

// ISO strings have millisecond resolution
const isoString = new Date(someTimestamp).toISOString();

req.validate({ weak: [isoString, requestedLanguage] });

When you use this approach, an ETag header will be sent, but the Last-Modified header will not be sent unless you also provide a lastModified date.

Don't use this approach if your clients need to use the If-Match header to avoid certain race conditions.

options.strong = null

If your clients need to use the If-Match header to avoid certain race conditions, you must use strong ETags instead of a weak ones. Strong ETags are much harder to generate correctly, so they're not recommended unless you truly need them. To use strong ETags, call req.validate() with the strong option, which behaves exactly like the weak option.

However, when using the strong option, you must adhere to strict requirements that cannot be enforced by this plugin:

  • A strong ETag must change whenever any observable change to the resource payload changes.
  • A strong ETag must be unique across all versions of a resource over time.
  • A strong ETag must be different for different representations of the same resource. For example, if content negotiation is used to conditionally apply gzip compression (Content-Encoding) to a resource, that resource's ETag must be different between the gzipped and non-gzipped versions. It's a common mistake to forget this. If you apply compression via a plugin (like vapr-compress), you should include the request's Accept-Encoding header in your ETag's array. Keep in mind that this also applies to any other transformations that you apply to the response body after it is generated.

For more details on the requirements of strong ETags, read here.

const crypto = require('crypto');

const hashedPayload = crypto.createHash('md5').update(payload).digest();
const etagParts = [hashedPayload];

if (req.headers.has('accept-encoding')) {
	etagParts.push(req.headers.get('accept-encoding'));
}

req.validate({ strong: etagParts });

When you use this approach, an ETag header will be sent, but the Last-Modified header will not be sent unless you also provide a lastModified date.

Package Sidebar

Install

npm i vapr-conditionals

Weekly Downloads

2

Version

0.2.1

License

MIT

Unpacked Size

14.7 kB

Total Files

7

Last publish

Collaborators

  • joshuawise