range-request-middleware
HTTP range request middleware.
Handles range request and partial response.
Compliant with RFC 9110, 14. Range Requests
Usage
Upon receipt of a range request, if the response satisfies the range requirement, merge it to a partial response.
import { rangeRequest } from "https://deno.land/x/range_request_middleware@$VERSION/mod.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");
yield:
HTTP/<VERSION> 206
Content-Range: bytes 5-9/26
Accept-Ranges: bytes
fghij
Multi-range request
For multi-range request, response body will convert to a multipart content.
It compliant with RFC 9110, 14.6. Media Type multipart/byteranges.
import { rangeRequest } from "https://deno.land/x/range_request_middleware@$VERSION/mod.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, 20-, -5" },
});
const response = await middleware(
request,
() => new Response("abcdefghijklmnopqrstuvwxyz"),
);
assertEquals(response.status, 206);
assertEquals(
response.headers.get(
"content-type",
),
"multipart/byteranges; boundary=<boundary-delimiter>",
);
assertEquals(
await response.text(),
`--<boundary-delimiter>
Content-Type: text/plain;charset=UTF-8
Content-Range: 5-9/26
fghij
--<boundary-delimiter>
Content-Type: text/plain;charset=UTF-8
Content-Range: 20-25/26
uvwxyz
--<boundary-delimiter>
Content-Type: text/plain;charset=UTF-8
Content-Range: 21-25/26
vwxyz
--<boundary-delimiter>--`,
);
yield:
HTTP/<VERSION> 206
Content-Type: multipart/byteranges; boundary=<BOUNDARY-DELIMITER>
Accept-Ranges: bytes
--<BOUNDARY-DELIMITER>
Content-Type: text/plain;charset=UTF-8
Content-Range: 5-9/26
fghij
--<BOUNDARY-DELIMITER>
Content-Type: text/plain;charset=UTF-8
Content-Range: 20-25/26
uvwxyz
--<BOUNDARY-DELIMITER>
Content-Type: text/plain;charset=UTF-8
Content-Range: 21-25/26
vwxyz
--<BOUNDARY-DELIMITER>--
Conditions
There are several conditions that must be met in order for middleware to execute.
If the following conditions are not met, invalid and the response will not modify.
- Request method is
GET
. - Request has
Range
header - Request does not include
If-Range
header - Request
Range
header is valid syntax - Request
Range
header is valid semantics - Response status code is
200
- Response does not include
Content-Range
header - Response does not include
Accept-Ranges
header or its value is notnone
- Response includes
Content-Type
header - Response body is readable
Note that if there is an If-Range
header, do nothing.
Unsatisfiable
If conditions is met and the following conditions are not met ,unsatisfiable, and it is not possible to meet partial response.
- If a valid ranges-specifier contains at least one satisfactory range-spec, as defined in the indicated range-unit
In this case, the handler response will merge to 416(Range Not Satisfiable) response.
A example of how unsatisfiable can happen:
If receive un unknown range unit.
import {
type Handler,
rangeRequest,
} from "https://deno.land/x/range_request_middleware@$VERSION/mod.ts";
import { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts";
declare const handler: Handler;
const middleware = rangeRequest();
const response = await middleware(
new Request("test:", { headers: { range: "<unknown-unit>=<other-range>" } }),
handler,
);
assertEquals(response.status, 416);
assert(response.headers.has("content-range"));
Satisfiable
If the conditions and unsatisfiable are met, satisfiable, and partial response will return.
The handler response will merge to 206(Partial Content) response.
Merging
The response of the handler and the response of Range.respond will merge .
The following elements are merged shallowly, giving priority to the Response
of Range.respond.
- HTTP Status code
- HTTP Content
- HTTP Headers
Range
Range
abstracts partial response.
Middleware factories can accept Range
objects and implement own range request
protocols.
Range
is the following structure:
Name | Type | Description |
---|---|---|
rangeUnit | string |
Corresponding range unit. |
respond | (response: Response, context: RangesSpecifier) => Response | Promise<Response> |
Return response from range request context. |
The middleware supports the following range request protocols by default:
bytes
(ByteRanges)
BytesRange
bytes
range unit is used to express subranges of a representation data’s octet
sequence.
ByteRange supports single and multiple range requests.
Compliant with RFC 9110, 14.1.2. Byte Ranges.
import {
BytesRange,
type IntRange,
type SuffixRange,
} from "https://deno.land/x/range_request_middleware@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
const bytesRange = new BytesRange();
const rangeUnit = "bytes";
declare const initResponse: Response;
declare const rangeSet: [IntRange, SuffixRange];
const response = await bytesRange.respond(initResponse, {
rangeUnit,
rangeSet,
});
assertEquals(bytesRange.rangeUnit, rangeUnit);
assertEquals(response.status, 206);
assertEquals(
response.headers.get("content-type"),
"multipart/byteranges; boundary=<BOUNDARY>",
);
Effects
Middleware may make changes to the following HTTP messages:
- HTTP Content
- HTTP Headers
- Accept-Ranges
- Content-Range
- Content-Type
- HTTP Status code
- 206(Partial Content)
- 416(Range Not Satisfiable)
License
Copyright © 2023-present httpland.
Released under the MIT license