-
Notifications
You must be signed in to change notification settings - Fork 0
/
middleware.ts
135 lines (119 loc) · 4.08 KB
/
middleware.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.
import { type Handler, type Middleware } from "./deps.ts";
import type { Precondition } from "./types.ts";
import {
applyPrecondition,
ascendPrecondition,
isNotSelectionOrModificationMethod,
isPreEvaluableStatus,
withoutConditionHeaders,
} from "./utils.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";
import { IfRange } from "./preconditions/if_range.ts";
/** Middleware options. */
export interface Options {
/** Precondition list.
*
* Default preconditions are as follows:
* - {@link IfMatch}
* - {@link IfNoneMatch}
* - {@link IfModifiedSince}
* - {@link IfUnmodifiedSince}
* - {@link IfRange}
*/
readonly preconditions?: Iterable<Precondition>;
}
/** Create HTTP conditional requests middleware.
*
* @example
* ```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("<body>", { headers: { etag: "<etag>" } });
* });
* const middleware = conditionalRequest(selectRepresentation);
* const request = new Request("<uri>", {
* headers: { "if-none-match": "<etag>" },
* });
* declare const _handler: Handler;
* const handler = spy(_handler);
*
* const response = await middleware(request, handler);
*
* assertSpyCalls(handler, 0);
* assertSpyCalls(selectRepresentation, 1);
* assertEquals(response.status, 304);
* assertFalse(response.body);
* ```
*/
export function conditionalRequest(
selectRepresentation: Handler,
options?: Options,
): Middleware {
// TODO(miyauci): use `toSort` someday
const preconditions = Array.from(
options?.preconditions ??
[
new IfMatch(),
new IfNoneMatch(),
new IfModifiedSince(),
new IfUnmodifiedSince(),
new IfRange(),
],
).sort(ascendPrecondition);
const curried = _handler.bind(null, selectRepresentation, preconditions);
return curried;
}
/** Handle preconditions with all contexts.
* @internal
*/
export async function _handler(
selectRepresentation: Handler,
preconditions: readonly Precondition[],
request: Request,
next: Handler,
): Promise<Response> {
/** Likewise, a server MUST ignore the conditional request header fields defined by this specification when received with a request method that does not involve the selection or modification of a selected representation, such as CONNECT, OPTIONS, or TRACE. */
if (isNotSelectionOrModificationMethod(request.method)) {
return next(request);
}
function hasPreconditionHeader(precondition: Precondition): boolean {
return request.headers.has(precondition.field);
}
const targetPreconditions = preconditions.filter(hasPreconditionHeader);
if (!targetPreconditions.length) return next(request);
const headers = withoutConditionHeaders(
request.headers,
preconditions.map(({ field }) => field),
);
const selectedRepresentation = await selectRepresentation(
new Request(request, { headers }),
);
// 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).
if (!isPreEvaluableStatus(selectedRepresentation.status)) {
return next(request);
}
for (const precondition of targetPreconditions) {
const maybeResponse = await applyPrecondition(
request,
selectedRepresentation,
precondition,
);
if (!maybeResponse) continue;
return maybeResponse;
}
return next(request);
}