Skip to content

Commit f43a46a

Browse files
committed
feat(middleware): add support if-range header field
1 parent 7119738 commit f43a46a

File tree

3 files changed

+118
-9
lines changed

3 files changed

+118
-9
lines changed

_dev_deps.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export { describe, it } from "https://deno.land/[email protected]/testing/bdd.ts";
2+
export {
3+
assert,
4+
assertEquals,
5+
assertExists,
6+
assertThrows,
7+
} from "https://deno.land/[email protected]/testing/asserts.ts";
8+
export {
9+
assertSpyCallArg,
10+
assertSpyCallArgs,
11+
assertSpyCalls,
12+
spy,
13+
} from "https://deno.land/[email protected]/testing/mock.ts";
14+
export { equalsResponse } from "https://deno.land/x/[email protected]/response.ts";
15+
export {
16+
ConditionalHeader,
17+
RangeHeader,
18+
RepresentationHeader,
19+
} from "https://deno.land/x/[email protected]/header.ts";
20+
export { Method } from "https://deno.land/x/[email protected]/method.ts";
21+
export { Status } from "https://deno.land/[email protected]/http/http_status.ts";

middleware.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,11 @@ import { IfNoneMatch } from "./preconditions/if_none_match.ts";
1313
import { IfMatch } from "./preconditions/if_match.ts";
1414
import { IfModifiedSince } from "./preconditions/if_modified_since.ts";
1515
import { IfUnmodifiedSince } from "./preconditions/if_unmodified_since.ts";
16-
17-
const DEFAULT_PRECONDITIONS = [
18-
IfMatch,
19-
IfNoneMatch,
20-
IfModifiedSince,
21-
IfUnmodifiedSince,
22-
];
16+
import { IfRange } from "./preconditions/if_range.ts";
2317

2418
/** Middleware options. */
2519
export interface Options {
26-
/** Apply precondition list. */
20+
/** Precondition list. */
2721
readonly preconditions?: Iterable<Precondition>;
2822
}
2923

@@ -58,7 +52,14 @@ export function conditionalRequest(
5852
): Middleware {
5953
// TODO(miyauci): use `toSort`
6054
const preconditions = Array.from(
61-
options?.preconditions ?? DEFAULT_PRECONDITIONS,
55+
options?.preconditions ??
56+
[
57+
new IfMatch(),
58+
new IfNoneMatch(),
59+
new IfModifiedSince(),
60+
new IfUnmodifiedSince(),
61+
new IfRange(),
62+
],
6263
).sort(ascendPrecondition);
6364

6465
return (request, next) =>

middleware_test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
equalsResponse,
88
it,
99
Method,
10+
RangeHeader,
1011
RepresentationHeader,
1112
spy,
1213
Status,
@@ -373,6 +374,92 @@ describe("conditionalRequest", () => {
373374
assert(response === initResponse);
374375
});
375376

377+
it("should return 206 response if request has if-range and range header and match etag", async () => {
378+
const ETAG = `""`;
379+
const selectRepresentation = spy(() =>
380+
new Response("abcdef", {
381+
headers: { [RepresentationHeader.ETag]: ETAG },
382+
})
383+
);
384+
const middleware = conditionalRequest(selectRepresentation);
385+
const request = new Request("test:", {
386+
headers: {
387+
[ConditionalHeader.IfRange]: ETAG,
388+
[RangeHeader.Range]: "bytes=-3",
389+
},
390+
});
391+
const handler = spy(() => new Response());
392+
393+
const response = await middleware(request, handler);
394+
395+
assertSpyCalls(selectRepresentation, 1);
396+
assertSpyCalls(handler, 0);
397+
398+
assert(
399+
await equalsResponse(
400+
response,
401+
new Response("def", {
402+
status: Status.PartialContent,
403+
headers: {
404+
[RangeHeader.ContentRange]: "bytes 3-5/6",
405+
[RepresentationHeader.ETag]: ETAG,
406+
},
407+
}),
408+
true,
409+
),
410+
);
411+
});
412+
413+
it("should return 206 response if request has if-range and range header and match last-modified", async () => {
414+
const lastModified = new Date("2000/1/1").toUTCString();
415+
const selectRepresentation = spy(() =>
416+
new Response("abcdef", {
417+
headers: { [RepresentationHeader.LastModified]: lastModified },
418+
})
419+
);
420+
const middleware = conditionalRequest(selectRepresentation);
421+
const request = new Request("test:", {
422+
headers: {
423+
[ConditionalHeader.IfRange]: lastModified,
424+
[RangeHeader.Range]: "bytes=4-, -3",
425+
},
426+
});
427+
const handler = spy(() => new Response());
428+
const response = await middleware(request, handler);
429+
const BOUNDARY = "1f8ac10f23c5b5bc1167bda84b833e5c057a77d2";
430+
431+
assertSpyCalls(selectRepresentation, 1);
432+
assertSpyCalls(handler, 0);
433+
434+
assert(
435+
await equalsResponse(
436+
response,
437+
new Response(
438+
`--${BOUNDARY}
439+
Content-Type: text/plain;charset=UTF-8
440+
Content-Range: bytes 4-5/6
441+
442+
ef
443+
--${BOUNDARY}
444+
Content-Type: text/plain;charset=UTF-8
445+
Content-Range: bytes 3-5/6
446+
447+
def
448+
--${BOUNDARY}--`,
449+
{
450+
status: Status.PartialContent,
451+
headers: {
452+
[RepresentationHeader.LastModified]: lastModified,
453+
[RepresentationHeader.ContentType]:
454+
`multipart/byteranges; boundary=${BOUNDARY}`,
455+
},
456+
},
457+
),
458+
true,
459+
),
460+
);
461+
});
462+
376463
it("should return next response if the filtered preconditions is empty", async () => {
377464
const selectedRepresentation = new Response("<body>", {
378465
headers: { etag: "<etag>" },

0 commit comments

Comments
 (0)