This repository was archived by the owner on Mar 17, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompress.ts
110 lines (93 loc) · 2.78 KB
/
compress.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Copyright 2022-latest the httpland authors. All rights reserved. MIT license.
import { Encode, Encoders, encodes } from "./encodes.ts";
import { isCompressible } from "./utils.ts";
import {
acceptsEncodings,
Handler,
mergeHeaders,
parseMediaType,
safeResponse,
} from "./deps.ts";
/** Compress options. */
export interface CompressOptions {
/** Filters compression targets.
* Only if `true` is returned, the response is compressed.
*
* By default, only content larger than 10kb will be compressed.
*
* @defaultValue {@link defaultFilter}
*/
readonly filter: (content: Uint8Array, context: FilterContext) => boolean;
}
/** Filter context. */
export interface FilterContext {
/** A cloned actual `Request` object. */
readonly request: Request;
/** A cloned actual `Response` object. */
readonly response: Response;
}
export const defaultFilter: CompressOptions["filter"] = (
content,
{ response },
) => {
if (content.byteLength < 1024_0) return false;
const type = response.headers.get("content-type");
if (!type) return false;
try {
const [mediaType] = parseMediaType(type);
return isCompressible(mediaType);
} catch {
return false;
}
};
/** Takes a handler and returns a handler with the response body compressed.
*
* ```ts
* import { withCompress } from "https://deno.land/x/http_compress@$VERSION/mod.ts";
*
* function handler(req: Request): Response {
* return new Response("Huge content");
* }
* Deno.serve(withCompress(handler));
* ```
*/
export function withCompress(
handler: Handler,
options: CompressOptions = { filter: defaultFilter },
): Handler {
return async (req) => {
const preferEncode = acceptsEncodings(
req,
...encodes,
"identity",
) as Encode | "identity" | undefined;
if (!preferEncode || !isSupportedEncode(preferEncode)) return handler(req);
return await safeResponse(async () => {
const res = await handler(req.clone());
const encoder = Encoders[preferEncode];
const hasCompressed = res.headers.has("Content-Encoding");
if (hasCompressed || res.bodyUsed) return res;
const newRes = res.clone();
const buffer = await newRes.arrayBuffer();
const u8 = new Uint8Array(buffer);
const context: FilterContext = {
request: req.clone(),
response: res.clone(),
};
const result = options.filter(u8, context);
if (!result) return res;
const body = encoder(u8);
const headers = mergeHeaders(
res.headers,
new Headers({
"Content-Encoding": preferEncode,
Vary: "Accept-Encoding",
}),
);
return new Response(body, { ...newRes, headers });
});
};
}
function isSupportedEncode(value: string): value is Encode {
return encodes.includes(value);
}