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, convert it to a partial response.
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");
yield:
HTTP/1.1 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/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, 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/1.1 206
Content-Type: multipart/byteranges; boundary=BOUNDARY
Accept-Ranges: bytes
--BOUNDARY
Content-Type: text/plain;charset=UTF-8
Content-Range: 5-9/26
fghij
--BOUNDARY
Content-Type: text/plain;charset=UTF-8
Content-Range: 20-25/26
uvwxyz
--BOUNDARY
Content-Type: text/plain;charset=UTF-8
Content-Range: 21-25/26
vwxyz
--BOUNDARY--
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 convert.
- Request method is
GET
. - Request includes
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 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 convert 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 the response will convert to 206(Partial Content) response.
Convert
Convert means a change without side effects.
For example, "convert a response to the 206 response" means to return a new response in which some or all of the following elements have been replaced from the original response.
- HTTP Content
- HTTP Status code
- HTTP Headers(shallow marge)
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