Skip to content

Commit

Permalink
feat(middleware): add rangeRequest, contentRange and acceptRanges mid…
Browse files Browse the repository at this point in the history
…dleware
  • Loading branch information
TomokiMiyauci committed Mar 22, 2023
1 parent ff78387 commit 03e8d6b
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 38 deletions.
59 changes: 51 additions & 8 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,63 @@
// deno-lint-ignore-file no-explicit-any
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.

import { type Middleware } from "./deps.ts";
import { withContentRange } from "./transform.ts";
import {
ConditionalHeader,
isNotEmpty,
isNull,
Method,
type Middleware,
RangeHeader,
} from "./deps.ts";
import { UnitLike, withAcceptRanges, withContentRange } from "./transform.ts";
import { BytesRange } from "./ranges/bytes.ts";
import { RangeUnit } from "./utils.ts";
import type { Range } from "./types.ts";

interface Options {
readonly ranges?: readonly Range<any>[];
const DefaultRanges = [new BytesRange()];

export function rangeRequest(ranges?: Iterable<Range>): Middleware {
const $ranges = ranges ?? DefaultRanges;
const units = Array.from($ranges).map((range) => range.unit);
const unitLike = isNotEmpty(units) ? units : RangeUnit.None;

const contentRangeMiddleware = contentRange($ranges);
const acceptRangesMiddleware = acceptRanges(unitLike);

return (request, next) => {
return contentRangeMiddleware(
request,
(request) => acceptRangesMiddleware(request, next),
);
};
}

export function contentRange(ranges?: Iterable<Range>): Middleware {
const $ranges = ranges ?? DefaultRanges;

return async (request, next) => {
const rangeValue = request.headers.get(RangeHeader.Range);

// A server MUST ignore a Range header field received with a request method that is unrecognized or for which range handling is not defined. For this specification, GET is the only method for which range handling is defined.
// @see https://www.rfc-editor.org/rfc/rfc9110#section-14.2-4
if (
request.method !== Method.Get ||
isNull(rangeValue) ||
request.headers.has(ConditionalHeader.IfRange)
) return next(request);

const response = await next(request);

return withContentRange(response, { ranges: $ranges, rangeValue });
};
}

export function range(options?: Options): Middleware {
const ranges = options?.ranges ?? [new BytesRange() as Range<any>];
export function acceptRanges(unitLike?: UnitLike): Middleware {
const rangeUnit = unitLike ?? RangeUnit.Bytes;

return async (request, next) => {
const response = await next(request);

return withContentRange(request, response, { ranges });
return withAcceptRanges(response, rangeUnit);
};
}
53 changes: 40 additions & 13 deletions middleware_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { range } from "./middleware.ts";
import { rangeRequest } from "./middleware.ts";
import {
assert,
describe,
Expand All @@ -9,38 +9,64 @@ import {
Status,
} from "./_dev_deps.ts";

describe("range", () => {
it("should", async () => {
const middleware = range();
const rangeRequest = new Request("test:", {
describe("rangeRequest", () => {
it("should return response what includes accept-ranges none", async () => {
const middleware = rangeRequest([]);
const request = new Request("test:", {
headers: { range: "bytes=5-9" },
});
const response = await middleware(
rangeRequest,
request,
() => new Response("abcdefghijklmnopqrstuvwxyz"),
);

assert(
await equalsResponse(
response,
new Response(`abcdefghijklmnopqrstuvwxyz`, {
headers: { [RangeHeader.AcceptRanges]: "none" },
}),
true,
),
);
});

it("should return response what includes content-range and accept-ranges headers", async () => {
const middleware = rangeRequest();
const request = new Request("test:", {
headers: { range: "bytes=5-9" },
});
const response = await middleware(
request,
() =>
new Response("abcdefghijklmnopqrstuvwxyz", {
headers: { [RepresentationHeader.ContentType]: "text/test" },
}),
);

assert(
await equalsResponse(
response,
new Response(`fghij`, {
status: Status.PartialContent,
headers: {
[RangeHeader.AcceptRanges]: "bytes",
[RangeHeader.ContentRange]: `bytes 5-9/26`,
[RepresentationHeader.ContentType]: "text/test",
},
}),
true,
),
);
});

it("should", async () => {
const middleware = range();
const rangeRequest = new Request("test:", {
it("should return response what body is multipart ranges", async () => {
const middleware = rangeRequest();
const request = new Request("test:", {
headers: { range: "bytes=5-9, 20-, -5" },
});
const response = await middleware(
rangeRequest,
request,
() => new Response("abcdefghijklmnopqrstuvwxyz"),
);

Expand All @@ -52,23 +78,24 @@ describe("range", () => {
new Response(
`--${boundary}
Content-Type: text/plain;charset=UTF-8
Content-Range: 5-9/26
Content-Range: bytes 5-9/26
fghij
--${boundary}
Content-Type: text/plain;charset=UTF-8
Content-Range: 20-25/26
Content-Range: bytes 20-25/26
uvwxyz
--${boundary}
Content-Type: text/plain;charset=UTF-8
Content-Range: 21-25/26
Content-Range: bytes 21-25/26
vwxyz
--${boundary}--`,
{
status: Status.PartialContent,
headers: {
[RangeHeader.AcceptRanges]: "bytes",
[RepresentationHeader.ContentType]:
`multipart/byteranges; boundary=${boundary}`,
},
Expand Down
21 changes: 4 additions & 17 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
import { type Middleware } from "./deps.ts";
import { withContentRange } from "./transform.ts";
import { BytesRange } from "./ranges/bytes.ts";
import type { Range } from "./types.ts";
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.

interface Options {
readonly ranges?: Iterable<Range>;
}

export default function rangeRequests(options?: Options): Middleware {
const ranges = options?.ranges ?? [new BytesRange()];

return async (request, next) => {
const response = await next(request);

return withContentRange(request, response, { ranges });
};
}
export { acceptRanges, contentRange, rangeRequest } from "./middleware.ts";
export { type Handler } from "./deps.ts";

0 comments on commit 03e8d6b

Please sign in to comment.