This repository has been archived by the owner on Mar 24, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmod.ts
101 lines (87 loc) · 2.92 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.
import { type Middleware } from "./deps.ts";
import type { Precondition } from "./types.ts";
import {
applyPrecondition,
ascendPrecondition,
isNotSelectionOrModificationMethod,
} from "./util.ts";
import IfNoneMatch from "./preconditions/if_none_match.ts";
import IfMatch from "./preconditions/if_match.ts";
import IfModifiedSince from "./preconditions/if_modified_since.ts";
import IfUnmodifiedSince from "./preconditions/if_unmodified_since.ts";
export type {
EvaluateCallback,
EvaluateContext,
Precondition,
RespondCallback,
} from "./types.ts";
const defaultPreconditions = [
IfMatch,
IfNoneMatch,
IfModifiedSince,
IfUnmodifiedSince,
];
/** Callback for selecting presentation data. */
export interface SelectPresentationCallback {
(request: Request): Response | Promise<Response>;
}
/** Middleware options. */
export interface Options {
/** Apply precondition list. */
readonly preconditions?: Iterable<Precondition>;
}
/** HTTP Conditional Requests middleware factory.
*
* @example
* ```ts
* import conditionalRequests from "https://deno.land/x/http_conditional_requests@$VERSION/mod.ts";
* import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
* import { assertSpyCalls, spy } from "https://deno.land/std/testing/mock.ts";
*
* const selectedRepresentation = new Response("<body>", {
* headers: { etag: "<etag>" },
* });
* const selectRepresentation = spy(() => selectedRepresentation);
* const middleware = conditionalRequests(selectRepresentation);
* const conditionalRequest = new Request("<uri>", {
* headers: { "if-none-match": "<etag>" },
* });
* const handler = spy(() => selectedRepresentation);
*
* const response = await middleware(conditionalRequest, handler);
*
* assertSpyCalls(handler, 0);
* assertSpyCalls(selectRepresentation, 1);
* assertEquals(response.status, 304);
* ```
*/
export default function conditionalRequests(
selectRepresentation: SelectPresentationCallback,
options?: Options,
): Middleware {
const preconditions = Array.from(
options?.preconditions ?? defaultPreconditions,
).toSorted(ascendPrecondition);
return async (request, next) => {
if (isNotSelectionOrModificationMethod(request.method)) {
return next(request);
}
const targetPreconditions = preconditions.filter(hasPreconditionHeader);
if (!targetPreconditions.length) return next(request);
const selectedRepresentation = await selectRepresentation(request);
for (const precondition of targetPreconditions) {
const result = await applyPrecondition(
request,
selectedRepresentation,
precondition,
);
if (result) return result;
}
return next(request);
function hasPreconditionHeader(precondition: Precondition): boolean {
return request.headers.has(precondition.header);
}
};
}