@httpland/range-request-middleware
Version:
HTTP range request middleware
64 lines (63 loc) • 2.65 kB
JavaScript
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.
import { ConditionalHeader, isNotEmpty, isNull, Method, RangeHeader, } from "./deps.js";
import { withAcceptRanges, withContentRange } from "./transform.js";
import { BytesRange } from "./ranges/bytes.js";
import { RangeUnit } from "./utils.js";
/** Create range request middleware.
*
* @example
* ```ts
* import { rangeRequest } from "https://deno.land/x/range_request_middleware@$VERSION/middleware.ts";
* import {
* assert,
* assertEquals,
* assertThrows,
* } from "https://deno.land/std/testing/asserts.ts";
*
* const middleware = rangeRequest();
* const request = new Request("test:", {
* headers: { range: "bytes=5-9" },
* });
* const response = await middleware(
* request,
* () => new Response("abcdefghijklmnopqrstuvwxyz"),
* );
*
* assertEquals(response.status, 206);
* assertEquals(response.headers.get("content-range"), "bytes 5-9/26");
* assertEquals(response.headers.get("accept-ranges"), "bytes");
* assertEquals(await response.text(), "fghij");
* ```
*/
export function rangeRequest(ranges) {
const $ranges = ranges ?? [new BytesRange()];
const units = Array.from($ranges).map((range) => range.rangeUnit);
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) {
const $ranges = ranges ?? [new BytesRange()];
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 acceptRanges(unitLike) {
const rangeUnit = unitLike ?? RangeUnit.Bytes;
return async (request, next) => {
const response = await next(request);
return withAcceptRanges(response, rangeUnit);
};
}