HTTP conditional request middleware
npm install @httpland/conditional-request-middleware






HTTP conditional request middleware.
Compliant with
RFC 9110, 13. Conditional Requests
For a definition of Universal HTTP middleware, see the
http-middleware project.
To evaluate precondition, you need to provide
select representation function to retrieve the
selected representation.
The following example evaluates the If-None-Match precondition and handle
response.
``ts
import {
conditionalRequest,
type Handler,
} from "https://deno.land/x/conditional_request_middleware@$VERSION/mod.ts";
import {
assertEquals,
assertFalse,
} from "https://deno.land/std/testing/asserts.ts";
import { assertSpyCalls, spy } from "https://deno.land/std/testing/mock.ts";
const selectRepresentation = spy((request: Request) => {
return new Response("
const response = await middleware(request, handler);
assertSpyCalls(handler, 0);
assertSpyCalls(selectRepresentation, 1);
assertEquals(response.status, 304);
assertFalse(response.body);
`
The evaluation of the pre-conditions is always done on the selected
representation.
You must provide a function to retrieve the representation.
Selecting representation is the following interface:
`ts`
interface SelectRepresentation {
(request: Request): Response | Promise
}
It is executed prior to the handler when a request with a precondition header is
received.
The Request object passed to select representation has fileted the conditional
header.
It satisfies the following requirement.
> A server MUST ignore all received preconditions if its response to the same
> request without those conditions, prior to processing the request content,
> would have been a status code other than a 2xx (Successful) or 412
> (Precondition Failed).
Middleware supports all preconditions compliant with
RFC 9110, 13.1. Preconditions
by default.
If you want to adapt only some of the preconditions, give a list of them.
Example of middleware that handles only If-None-Match and If-Modified-Since
headers:
`ts
import {
conditionalRequest,
type Handler,
IfModifiedSince,
IfNoneMatch,
} from "https://deno.land/x/conditional_request_middleware@$VERSION/mod.ts";
declare const selectRepresentation: Handler;
const middleware = conditionalRequest(selectRepresentation, {
preconditions: [new IfNoneMatch(), new IfModifiedSince()],
});
`
Don't worry about the order of preconditions. They will be sorted appropriately
based on the
13.2.2. Precedence of Preconditions.
The Middleware factory default values are as follows:
`ts
import {
BytesRange,
conditionalRequest,
type Handler,
IfMatch,
IfModifiedSince,
IfNoneMatch,
IfRange,
IfUnmodifiedSince,
} from "https://deno.land/x/conditional_request_middleware@$VERSION/mod.ts";
declare const selectRepresentation: Handler;
const DEFAULT_PRECONDITIONS = [
new IfMatch(),
new IfNoneMatch(),
new IfModifiedSince(),
new IfUnmodifiedSince(),
new IfRange([new BytesRange()]),
];
const middleware = conditionalRequest(selectRepresentation, {
preconditions: DEFAULT_PRECONDITIONS,
});
`
Precondition is following structured object.
`ts
/* Precondition API. /
export interface Precondition {
/* Precondition header field name. /
readonly field: string;
/** Definition of precondition evaluation.
* If return value is void, it represents ignore this precondition.
*/
evaluate(
request: Request,
selectedRepresentation: Response,
): boolean | void | Promise
/** Called after {@link Precondition.evaluate}.
* If return response, it must not perform the requested method.
* If return value is void, it represents ignore this precondition.
*/
respond(
request: Request,
selectedRepresentation: Response,
result: boolean,
): Response | void | Promise
}
`
Precondition abstracts the evaluation of a precondition and its response.
Provide all preconditions compliant with
RFC 9110, 13.1. Preconditions
- If-Match
- If-None-Match
- If-Modified-Since
- If-Unmodified-Since
- If-Range
If you implement a Precondition that is not in the specification, make sure
extensibility of preconditions.
If-Match header field precondition.
`ts
import { IfMatch } from "https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_match.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
const precondition = new IfMatch();
const request = new Request("
headers: { "if-match": "
});
const selectedRepresentation = new Response("
headers: { etag: "
});
declare const evalResult: false;
assertEquals(precondition.field, "if-match");
assertEquals(
precondition.evaluate(request, selectedRepresentation),
evalResult,
);
assertEquals(
precondition.respond(request, selectedRepresentation, evalResult)?.status,
412,
);
`
#### Effects
Precondition will effect following:
If evaluation is false:
- HTTP content
- HTTP response status
- 412 (Precondition Failed)
- HTTP headers
- Representation headers
If-None-Match header field precondition.
`ts
import { IfNoneMatch } from "https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_none_match.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
const precondition = new IfNoneMatch();
const request = new Request("
headers: { "if-none-match": "
});
const selectedRepresentation = new Response("
headers: { etag: "
});
declare const evalResult: false;
assertEquals(precondition.field, "if-none-match");
assertEquals(
precondition.evaluate(request, selectedRepresentation),
evalResult,
);
assertEquals(
precondition.respond(request, selectedRepresentation, evalResult)?.status,
304,
);
`
#### Effects
Precondition will effect following:
If evaluation is false:
- HTTP content
- HTTP response status
- 304 (Not Modified)
- 412 (Precondition Failed)
- HTTP headers
- Representation headers
If-Modified-Since header field precondition.
`ts
import { IfModifiedSince } from "https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_modified_since.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
const precondition = new IfModifiedSince();
const request = new Request("
headers: { "if-modified-since": "
});
const selectedRepresentation = new Response("
headers: { "last-modified": "
});
declare const evalResult: false;
assertEquals(precondition.field, "if-modified-since");
assertEquals(
precondition.evaluate(request, selectedRepresentation),
evalResult,
);
assertEquals(
precondition.respond(request, selectedRepresentation, evalResult)?.status,
304,
);
`
#### Effects
Precondition will effect following:
If evaluation is false:
- HTTP content
- HTTP response status
- 304 (Not Modified)
- HTTP headers
- Content-Type
- Content-Encoding
- Content-Length
- Content-Language
If-Unmodified-Since header field precondition.
`ts
import { IfUnmodifiedSince } from "https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_unmodified_since.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
const precondition = new IfUnmodifiedSince();
const request = new Request("
headers: { "if-unmodified-since": "
});
const selectedRepresentation = new Response("
headers: { "last-modified": "
});
declare const evalResult: false;
assertEquals(precondition.field, "if-unmodified-since");
assertEquals(
precondition.evaluate(request, selectedRepresentation),
evalResult,
);
assertEquals(
precondition.respond(request, selectedRepresentation, evalResult)?.status,
412,
);
`
#### Effects
Precondition will effect following:
If evaluation is false:
- HTTP content
- HTTP response status
- 412 (Precondition Failed)
- HTTP headers
- Representation headers
If-Range header field precondition.
`ts
import { IfRange } from "https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_range.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
const precondition = new IfRange();
const request = new Request("
headers: { "if-range": "
});
const selectedRepresentation = new Response("
headers: { etag: "
});
declare const evalResult: false;
assertEquals(precondition.field, "if-range");
assertEquals(
precondition.evaluate(request, selectedRepresentation),
evalResult,
);
assertEquals(
(await precondition.respond(request, selectedRepresentation, evalResult))
?.status,
206,
);
`
#### Effects
Precondition will effect following:
If evaluation is true:
- HTTP content
- HTTP response status
- 206 (Partial Content)
- 416 (Range Not Satisfiable)
- HTTP headers
- Content-Range
- Content-Type
Middleware will execute only if the following conditions are met:
- Request is conditional request
- Request method is not CONNECT, OPTIONS or TRACE2xx
- Select representation status is or 412`
Copyright © 2023-present httpland.
Released under the MIT license